#!/usr/bin/python2.5
#
# Copyright 2009 the Melange authors.
#
# 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.

"""GHOP specific views for Programs.
"""

__authors__ = [
    '"Madhusudan.C.S" <madhusudancs@gmail.com>',
    '"Lennard de Rijk" <ljvderijk@gmail.com>',
  ]


import datetime
import os

from django import forms
from django import http
from django.utils.translation import ugettext

from soc.logic import cleaning
from soc.logic import dicts
from soc.logic.helper import timeline as timeline_helper
from soc.logic.models import host as host_logic
from soc.views import out_of_band
from soc.views import helper
from soc.views.helper import decorators
from soc.views.helper import dynaform
from soc.views.helper import lists
from soc.views.helper import params as params_helper
from soc.views.helper import redirects
from soc.views.helper import widgets
from soc.views.models import document as document_view
from soc.views.models import program 
from soc.views.sitemap import sidebar

import soc.cache.logic

from soc.modules.ghop.logic.models import mentor as ghop_mentor_logic
from soc.modules.ghop.logic.models import org_admin as ghop_org_admin_logic
from soc.modules.ghop.logic.models import program as ghop_program_logic
from soc.modules.ghop.logic.models import student as ghop_student_logic
from soc.modules.ghop.logic.models import task as ghop_task_logic
from soc.modules.ghop.models import task as ghop_task_model
from soc.modules.ghop.views.helper import access as ghop_access
from soc.modules.ghop.views.helper import redirects as ghop_redirects

import soc.modules.ghop.logic.models.program


class View(program.View):
  """View methods for the GHOP Program model.
  """

  DEF_PARTICIPATING_ORGS_MSG_FMT = ugettext(
      'The following is a list of all the participating organizations under '
      'the programme %(name)s. To know more about each organization and see '
      'the tasks published by them please visit the corresponding links.')

  DEF_TASK_QUOTA_ALLOCATION_MSG = ugettext(
      "Use this view to assign task quotas.")

  DEF_TASK_QUOTA_ERROR_MSG_FMT = ugettext(
      "Task Quota limit for the organizations %s do not contain"
      " a valid number(>0) and has not been updated.")

  def __init__(self, params=None):
    """Defines the fields and methods required for the program View class
    to provide the user with list, public, create, edit and delete views.

    Params:
      params: a dict with params for this View
    """

    rights = ghop_access.GHOPChecker(params)
    rights['show'] = ['allow']
    rights['create'] = [('checkSeeded', ['checkHasActiveRoleForScope',
                                         host_logic.logic])]
    rights['edit'] = [('checkIsHostForProgram',
                       [ghop_program_logic.logic])]
    rights['delete'] = ['checkIsDeveloper']
    rights['accepted_orgs'] = [('checkIsAfterEvent',
        ['student_signup_start',
         '__all__', ghop_program_logic.logic])]
    rights['task_difficulty'] = [('checkIsHostForProgram',
                                  [ghop_program_logic.logic])]
    rights['task_type'] = [('checkIsHostForProgram',
                            [ghop_program_logic.logic])]
    rights['difficulty_tag_edit'] = [('checkIsHostForProgram',
                                      [ghop_program_logic.logic])]
    rights['type_tag_edit'] = [('checkIsHostForProgram',
                                [ghop_program_logic.logic])]

    new_params = {}
    new_params['logic'] = soc.modules.ghop.logic.models.program.logic
    new_params['rights'] = rights

    new_params['name'] = "GHOP Program"
    new_params['module_name'] = "program"
    new_params['sidebar_grouping'] = 'Programs'

    new_params['module_package'] = 'soc.modules.ghop.views.models'
    new_params['url_name'] = 'ghop/program'

    # TODO: this list can be reduced after GSoC has been moved
    new_params['extra_dynaexclude'] = ['min_slots', 'max_slots',
                                       'slot_allocation', 'allocations_visible',
                                       'task_difficulties', 'task_types',
                                       ]

    patterns = []
    patterns += [
        (r'^%(url_name)s/(?P<access_type>assign_task_quotas)/%(key_fields)s$',
          '%(module_package)s.%(module_name)s.assign_task_quotas',
          'Assign task quota limits'),
        (r'^%(url_name)s/(?P<access_type>task_difficulty)/%(key_fields)s$',
         '%(module_package)s.%(module_name)s.task_difficulty_edit',
         'Edit Task Difficulty Tags'),
        (r'^%(url_name)s/(?P<access_type>task_type)/%(key_fields)s$',
         '%(module_package)s.%(module_name)s.task_type_edit',
         'Edit Task Type Tags'),
        (r'^%(url_name)s/(?P<access_type>difficulty_tag_edit)$',
         '%(module_package)s.%(module_name)s.difficulty_tag_edit',
         'Edit a Difficulty Tag'),
        (r'^%(url_name)s/(?P<access_type>type_tag_edit)$',
         '%(module_package)s.%(module_name)s.task_type_tag_edit',
         'Edit a Task Type Tag'),
        ]

    new_params['extra_django_patterns'] = patterns

    params = dicts.merge(params, new_params, sub_merge=True)

    super(View, self).__init__(params=params)

    dynafields = [
        {'name': 'overview_task_difficulties',
         'base': forms.CharField,
         'label': 'Task Difficulty Levels',
         'group': 'Task Settings',
         'widget': widgets.ReadOnlyInput(),
         'required': False,
         'help_text': ugettext('Lists all the difficulty levels that '
                               'can be assigned to a task. Edit them '
                               'from the Program menu on sidebar.'),
         },
         {'name': 'overview_task_types',
         'base': forms.CharField,
         'label': 'Task Type Tags',
         'group': 'Task Settings',
         'widget': widgets.ReadOnlyInput(),
         'required': False,
         'help_text': ugettext('Lists all the types a task can be in. '
                               'Edit them from the Program menu on sidebar.'),
         },
        ]

    dynaproperties = params_helper.getDynaFields(dynafields)

    edit_form = dynaform.extendDynaForm(
        dynaform=self._params['edit_form'],
        dynaproperties=dynaproperties)

    self._params['edit_form'] = edit_form

  def _editGet(self, request, entity, form):
    """See base.View._editGet().
    """

    # TODO: can't a simple join operation do this?
    tds = ghop_task_model.TaskDifficultyTag.get_by_scope(entity)
    if tds:
      td_str = ''
      for td in tds[:-1]:
        td_str += str(td) + ', '

      td_str += str(tds[-1])

      form.fields['overview_task_difficulties'].initial = td_str

    tts = ghop_task_model.TaskTypeTag.get_by_scope(entity)
    if tts:
      tt_str = ''
      for tt in tts[:-1]:
        tt_str += str(tt) + ', '

      tt_str += str(tts[-1])

      form.fields['overview_task_types'].initial = tt_str

    return super(View, self)._editGet(request, entity, form)

  @decorators.merge_params
  @decorators.check_access
  def assignTaskQuotas(self, request, access_type, page_name=None,
                       params=None, filter=None, **kwargs):
    """View that allows to assign task quotas for accepted GHOP organization.

    This view allows the program admin to set the task quota limits
    and change them at any time when the program is active. 
    """

    # TODO: Once GAE Task APIs arrive, this view will be managed by them
    program = ghop_program_logic.logic.getFromKeyFieldsOr404(kwargs)

    from soc.modules.ghop.views.models import \
        organization as ghop_organization_view

    org_params = ghop_organization_view.view.getParams().copy()

    context = {}

    if request.method == 'POST':
      return self.assignTaskQuotasPost(request, context, org_params, 
                                       page_name, params, program,
                                       **kwargs)
    else: # request.method == 'GET'
      return self.assignTaskQuotasGet(request, context, org_params,
                                      page_name, params, program, 
                                      **kwargs)

  def assignTaskQuotasPost(self, request, context, org_params, 
                           page_name, params, entity, **kwargs):
    """Handles the POST request for the task quota allocation page.

    Args:
        entity: the program entity
        rest: see base.View.public()
    """

    ghop_org_logic = org_params['logic']

    error_orgs = ''
    for link_id, task_count in request.POST.items():
      fields = {
          'link_id': link_id,
          'scope': entity,
          'scope_path': entity.key().id_or_name(),
          }
      key_name = ghop_org_logic.getKeyNameFromFields(fields)

      try:
        task_count = int(task_count)
        if task_count >= 0:
          fields['task_quota_limit'] = task_count
          ghop_org_logic.updateOrCreateFromKeyName(fields, key_name)
        else:
          raise ValueError
      except ValueError:
        org_entity = ghop_org_logic.getFromKeyName(key_name)
        error_orgs += org_entity.name + ', ' 

    if error_orgs:
      context['error_message'] = self.DEF_TASK_QUOTA_ERROR_MSG_FMT % (
          error_orgs[:-2])    

      return self.assignTaskQuotasGet(request, context, org_params,
                                      page_name, params, entity,
                                      **kwargs) 

    # redirect to the same page
    return http.HttpResponseRedirect('')

  def assignTaskQuotasGet(self, request, context, org_params, 
                          page_name, params, entity, **kwargs):
    """Handles the GET request for the task quota allocation page.

    Args:
        entity: the program entity
        rest see base.View.public()
    """

    org_params['list_template'] = ('modules/ghop/program/'
        'allocation/allocation.html')
    org_params['list_heading'] = ('modules/ghop/program/'
        'allocation/heading.html')
    org_params['list_row'] = 'modules/ghop/program/allocation/row.html'
    org_params['list_pagination'] = 'soc/list/no_pagination.html'

    description = self.DEF_TASK_QUOTA_ALLOCATION_MSG

    filter = {
        'scope': entity,
        'status': 'active',
        }

    content = self._getAcceptedOrgsList(description, org_params,
                                        filter, False)

    contents = [content]

    return self._list(request, org_params, contents, page_name, context)

  @decorators.merge_params
  def getExtraMenus(self, id, user, params=None):
    """See soc.views.models.program.View.getExtraMenus().
    """

    logic = params['logic']
    rights = params['rights']

    # only get all invisible and visible programs
    fields = {'status': ['invisible', 'visible']}
    entities = logic.getForFields(fields)

    menus = []

    rights.setCurrentUser(id, user)

    for entity in entities:
      items = []

      if entity.status == 'visible':
        # show the documents for this program, even for not logged in users
        items += document_view.view.getMenusForScope(entity, params)
        items += self._getTimeDependentEntries(entity, params, id, user)

      try:
        # check if the current user is a host for this program
        rights.doCachedCheck('checkIsHostForProgram',
                             {'scope_path': entity.scope_path,
                              'link_id': entity.link_id}, [])

        if entity.status == 'invisible':
          # still add the document links so hosts can see how it looks like
          items += document_view.view.getMenusForScope(entity, params)
          items += self._getTimeDependentEntries(entity, params, id, user)

        items += [(redirects.getReviewOverviewRedirect(
            entity, {'url_name': 'ghop/org_app', 'scope_view': self}),
            "Review Organization Applications", 'any_access')]
        # add link to edit Program Profile
        items += [(redirects.getEditRedirect(entity, params),
            'Edit Program Profile', 'any_access')]
        # add link to Assign Task Quota limits
        items += [(ghop_redirects.getAssignTaskQuotasRedirect(entity, params),
            'Assign Task Quota limits', 'any_access')]
        # add link to edit Program Timeline
        items += [(redirects.getEditRedirect(
            entity, {'url_name': 'ghop/timeline'}),
            "Edit Program Timeline", 'any_access')]
        # add link to create a new Program Document
        items += [(redirects.getCreateDocumentRedirect(entity, 'ghop/program'),
            "Create a New Document", 'any_access')]
        # add link to list all Program Document
        items += [(redirects.getListDocumentsRedirect(entity, 'ghop/program'),
            "List Documents", 'any_access')]
        # add link to edit Task Difficulty Levels
        items += [(ghop_redirects.getDifficultyEditRedirect(
            entity, {'url_name': 'ghop/program'}),
            "Edit Task Difficulty Levels", 'any_access')]
        # add link to edit Task Type Tags
        items += [(ghop_redirects.getTaskTypeEditRedirect(
            entity, {'url_name': 'ghop/program'}),
            "Edit Task Type Tags", 'any_access')]

      except out_of_band.Error:
        pass

      items = sidebar.getSidebarMenu(id, user, items, params=params)
      if not items:
        continue

      menu = {}
      menu['heading'] = entity.short_name
      menu['items'] = items
      menu['group'] = 'Programs'
      menus.append(menu)
    
    return menus

  def _getTimeDependentEntries(self, ghop_program_entity, params, id, user):
    """Returns a list with time dependent menu items.
    """

    items = []

    timeline_entity = ghop_program_entity.timeline

    # get the student entity for this user and program
    filter = {'user': user,
              'scope': ghop_program_entity,
              'status': 'active'}
    student_entity = ghop_student_logic.logic.getForFields(filter, unique=True)

    if student_entity:
      items += self._getStudentEntries(ghop_program_entity, student_entity,
                                       params, id, user)

    # get mentor and org_admin entity for this user and program
    filter = {'user': user,
              'program': ghop_program_entity,
              'status': 'active'}
    mentor_entity = ghop_mentor_logic.logic.getForFields(filter, unique=True)
    org_admin_entity = ghop_org_admin_logic.logic.getForFields(
        filter, unique=True)

    if mentor_entity or org_admin_entity:
      items += self._getOrganizationEntries(
          ghop_program_entity, org_admin_entity,
          mentor_entity, params, id, user)

    if user and not (student_entity or mentor_entity or org_admin_entity):
      if timeline_helper.isActivePeriod(timeline_entity, 'student_signup'):
        # this user does not have a role yet for this program
        items += [('/ghop/student/apply/%s' % (
            ghop_program_entity.key().id_or_name()),
            "Register as a Student", 'any_access')]

    if timeline_helper.isAfterEvent(timeline_entity, 'org_signup_start'):
      url = redirects.getAcceptedOrgsRedirect(
          ghop_program_entity, params)
      # add a link to list all the organizations
      items += [(url, "List participating Organizations", 'any_access')]

    return items

  def _getStudentEntries(self, ghop_program_entity, student_entity, 
                         params, id, user):
    """Returns a list with menu items for students in a specific program.
    """

    items = []

    timeline_entity = ghop_program_entity.timeline

    if timeline_helper.isAfterEvent(timeline_entity,
                                   'student_signup_start'):
      # add a link to show all projects
      items += [(redirects.getListProjectsRedirect(ghop_program_entity,
          {'url_name':'ghop/task'}),
          "List my Tasks", 'any_access')]

    items += [(redirects.getEditRedirect(student_entity, 
        {'url_name': 'ghop/student'}),
        "Edit my Student Profile", 'any_access')]

    items += [(redirects.getManageRedirect(student_entity,
        {'url_name':'ghop/student'}),
        "Resign as a Student", 'any_access')]

    return items

  @decorators.merge_params
  @decorators.check_access
  def taskDifficultyEdit(self, request, access_type, page_name=None,
                         params=None, filter=None, **kwargs):
    """View method used to edit Difficulty Level tags.
    """

    params = dicts.merge(params, self._params)

    try:
      entity = self._logic.getFromKeyFieldsOr404(kwargs)
    except out_of_band.Error, error:
      return helper.responses.errorResponse(
          error, request, template=params['error_public'])

    context = helper.responses.getUniversalContext(request)
    helper.responses.useJavaScript(context, params['js_uses_all'])
    context['page_name'] = page_name

    context['program_key_name'] = entity.key().name()

    context['difficulties'] = ghop_task_model.TaskDifficultyTag.get_by_scope(
        entity)

    params['edit_template'] = 'modules/ghop/program/tag/difficulty.html'

    return self._constructResponse(request, entity, context, None, params)

  @decorators.merge_params
  @decorators.check_access
  def difficultyTagEdit(self, request, access_type, page_name=None,
                        params=None, filter=None, **kwargs):
    """View method used to edit a supplied Difficulty level tag.
    """

    get_params = request.GET

    order = get_params.getlist('order')
    program_key_name = get_params.get('program_key_name')

    program_entity = ghop_program_logic.logic.getFromKeyName(
        program_key_name)

    if order:
      for index, elem in enumerate(order):
        ghop_task_model.TaskDifficultyTag.update_order(
              program_entity, elem, index)
      return http.HttpResponse()
    else:
      tag_data = get_params.getlist('tag_data')

      tag_name = tag_data[0].strip()
      tag_value = tag_data[1].strip()

      if tag_name:
        if not tag_value:
          ghop_task_model.TaskDifficultyTag.delete_tag(
              program_entity, tag_name)
        elif tag_name != tag_value:
          ghop_task_model.TaskDifficultyTag.copy_tag(
              program_entity, tag_name, tag_value)
      else:
        ghop_task_model.TaskDifficultyTag.get_or_create(
            program_entity, tag_value)

      return http.HttpResponse(tag_value)

  @decorators.merge_params
  @decorators.check_access
  def taskTypeEdit(self, request, access_type, page_name=None,
                   params=None, filter=None, **kwargs):
    """View method used to edit Task Type tags.
    """

    params = dicts.merge(params, self._params)

    try:
      entity = self._logic.getFromKeyFieldsOr404(kwargs)
    except out_of_band.Error, error:
      return helper.responses.errorResponse(
          error, request, template=params['error_public'])

    context = helper.responses.getUniversalContext(request)
    helper.responses.useJavaScript(context, params['js_uses_all'])
    context['page_name'] = page_name

    context['program_key_name'] = entity.key().name()

    context['task_types'] = ghop_task_model.TaskTypeTag.get_by_scope(
        entity)

    params['edit_template'] = 'modules/ghop/program/tag/task_type.html'

    return self._constructResponse(request, entity, context, None, params)

  @decorators.merge_params
  @decorators.check_access
  def taskTypeTagEdit(self, request, access_type, page_name=None,
                      params=None, filter=None, **kwargs):
    """View method used to edit a supplied Task Type tag.
    """

    get_params = request.GET

    order = get_params.getlist('order')
    program_key_name = get_params.get('program_key_name')

    program_entity = ghop_program_logic.logic.getFromKeyName(
        program_key_name)

    if order:
      for index, elem in enumerate(order):
        ghop_task_model.TaskTypeTag.update_order(
              program_entity, elem, index)
      return http.HttpResponse()
    else:
      tag_data = get_params.getlist('tag_data')
      program_key_name = get_params.get('program_key_name')

      tag_name = tag_data[0].strip()
      tag_value = tag_data[1].strip()

      program_entity = ghop_program_logic.logic.getFromKeyName(
          program_key_name)

      if tag_name:
        if not tag_value:
          ghop_task_model.TaskTypeTag.delete_tag(
              program_entity, tag_name)
        elif tag_name != tag_value:
          ghop_task_model.TaskTypeTag.copy_tag(
              program_entity, tag_name, tag_value)
      else:
        ghop_task_model.TaskTypeTag.get_or_create(program_entity, tag_value)

      return http.HttpResponse(tag_value)

  @decorators.merge_params
  @decorators.check_access
  def acceptedOrgs(self, request, access_type,
                   page_name=None, params=None, filter=None, **kwargs):
    """List all the accepted orgs for the given program.
    """

    from soc.modules.ghop.views.models.organization import view as \
        ghop_org_view

    contents = []
    logic = params['logic']

    ghop_program_entity = logic.getFromKeyFieldsOr404(kwargs)

    ao_params = ghop_org_view.getParams().copy()
    ao_params['list_action'] = (redirects.getHomeRedirect,
                                ao_params)
    ao_params['list_description'] = self.DEF_PARTICIPATING_ORGS_MSG_FMT % {
        'name': ghop_program_entity.name
        }

    filter = {
        'scope': ghop_program_entity,
        'status': ['new', 'active'],
        }

    order = ['name']

    ao_list = lists.getListContent(request, ao_params, filter=filter,
                                   order=order, idx=0)

    contents.append(ao_list)

    params = params.copy()
    params['list_msg'] = ghop_program_entity.accepted_orgs_msg

    return self._list(request, params, contents, page_name)


view = View()

admin = decorators.view(view.admin)
accepted_orgs = decorators.view(view.acceptedOrgs)
assign_task_quotas = decorators.view(view.assignTaskQuotas)
create = decorators.view(view.create)
delete = decorators.view(view.delete)
edit = decorators.view(view.edit)
list = decorators.view(view.list)
public = decorators.view(view.public)
export = decorators.view(view.export)
home = decorators.view(view.home)
difficulty_tag_edit = decorators.view(view.difficultyTagEdit)
task_type_tag_edit = decorators.view(view.taskTypeTagEdit)
task_difficulty_edit = decorators.view(view.taskDifficultyEdit)
task_type_edit = decorators.view(view.taskTypeEdit)
