import sqlalchemy
from main.model import Computer, OrganizativeUnit
from clientjob.model import Job
from plugins.disk_image.jobs import DiskCurrentStateJob, CloneJob, RestoreDiskProfileJob
from model import DiskProfile, Disk, Image, Partition
import datetime
import time
import web
from web import form
from decorators import pi18n
from decimal import Decimal
from user.views import get_user
from utils import fancy_datetime


class DPView(object):

    def get_render(self):
        return web.ctx.render.plugins.disk_image
    render = property(get_render)

    def get_orm(self):
        return web.ctx.orm
    orm = property(get_orm)


class DiskProfileListView:

    def GET(self):
        profiles = web.ctx.orm.query(DiskProfile).all()
        return web.ctx.render.plugins.disk_image.\
                    disk_profile_list(profiles)


class DiskProfileView:

    def GET(self, pd_id):
        q = web.ctx.orm.query(DiskProfile).filter_by(id=pd_id)
        if not q.count():
            raise web.notfound()

        profile = q.one()

        return web.ctx.render.plugins.disk_image.disk_state(profile)


class CurrentState:
    '''
    This a intermediate view. First it looks for a previus job sent to
    the computer to know its disk state. If this job is recently
    the user will be redirect to the view of that disk state.
    If not a new job is sent.
    '''

    EXPIRED_JOB = datetime.timedelta(minutes = 15)
    def GET(self, computer_name, force=False):
        computer = web.ctx.orm.query(Computer).filter(Computer.name ==
                computer_name).first()
        if not computer:
            return web.notfound()

        if not computer.online:
            return web.ctx.render.plugins.disk_image.\
                    diskstate_computer_offline(computer)

        image_plugin = web.ctx.plugin_manager.get_plugin_by_name('disk_image')

        if force:
            job = None
        else:
            job = web.ctx.orm.query(Job)
            job = job.filter(Job.class_name == 'DiskCurrentStateJob')
            job = job.filter(Job.computer == computer)
            job = job.filter(Job.status != 'CANCELLED')
            job = job.filter(Job.status != 'ERROR')
            job = job.order_by(sqlalchemy.desc(Job.last_modified_date))
            job = job.first()

        if job and datetime.datetime.now() - job.last_modified_date\
                > CurrentState.EXPIRED_JOB:
            url = image_plugin.get_view_disk_state_url(job.id)

        else:
            new_job_id = image_plugin.create_new_current_state(computer)
            url = image_plugin.get_view_disk_state_url(new_job_id)

        raise web.seeother(url)


class ForceCurrentState(CurrentState):

    def GET(self, computer_name):
        return CurrentState.GET(self, computer_name, force=True)


class ShowDiskProfile:
    def GET(self, disk_profile_id, child_id, child_is_a_computer):
        orm = web.ctx.orm
        disk_profile = orm.query(DiskProfile).filter(
                DiskProfile.id == disk_profile_id).firts()
        if not disk_profile:
            return web.notfound()

class DiskState:
    @pi18n
    def GET(self, job_id): #, noupdate = False, force = False):
        job = web.ctx.orm.query(Job)
        job = job.filter(Job.class_name == 'DiskCurrentStateJob')
        job = job.filter(Job.id == job_id).first()

        if not job:
            raise web.notfound()

        image_plugin = web.ctx.plugin_manager.get_plugin_by_name('disk_image')
        if job.status in ('CANCELLED', 'ERROR'):
            url = image_plugin.get_new_disk_state_url(job.computer.name)
            return web.ctx.render.plugins.disk_image.diskstate_error(url)

        elif job.status in ('None', 'CREATED', 'WAITING', 'SENT'):
            web.ctx.notify(_('Disk state information for %(comp_name)s is '
                'comming. This process can take some minutes. Please wait.') %\
                    dict(comp_name=job.computer.name), 'info')
            from clientjob.jobview import JobView
            return JobView().GET(job.id)

        elif job.status == 'INPROGRESS':
            web.ctx.notify(_('Disk state information for %(comp_name)s is '
                'comming. This process can take some minutes. Please wait.') %\
                    dict(comp_name=job.computer.name), 'info')
            from clientjob.jobview import JobView
            return JobView().GET(job.id)

        elif job.status == 'FINISHED':
            web.ctx.notify(_("<h3><a href=\"%(disk_profile_url)s\">%(comp)s disk profile"
                "</a> has been retrieved</h3> If you aren't sure that this information "
                "is recent, you can <a href=\"%(url)s\">update disk profile</a>.") %\
                    dict(
                        comp=job.computer.name,
                        url=image_plugin.fill_url(ForceCurrentState, job.computer.name),
                        disk_profile_url=image_plugin.get_view_disk_state_url(job_id)
                    ),"info")

        profile = job.generate_profile()
        return web.ctx.render.plugins.disk_image.disk_state(profile)


class SaveDiskState:
    @pi18n
    def GET(self, job_id):
        job = web.ctx.orm.query(Job)
        job = job.filter(Job.class_name == 'DiskCurrentStateJob')
        job = job.filter(Job.id == job_id).first()

        if not job:
            raise web.notfound()

        if job.status != 'FINISHED':
            web.ctx.notify(_('Disk state information for %(comp_name)s is '
                'comming. This process can take some minutes. Please wait.') %\
                    dict(comp_name=job.computer.name), 'info')
            from clientjob.jobview import JobView
            return JobView().GET(job.id)

        image_plugin = web.ctx.plugin_manager.get_plugin_by_name('disk_image')
        profile = job.generate_profile()
        profile.computers.append(job.computer)
        web.ctx.orm.commit()

        job = CloneJob(job.computer.id)
        job.send()
        image_plugin = web.ctx.plugin_manager.get_plugin_by_name('disk_image')
        raise web.seeother('/jobs/%d' % job.id)

class AdminImages:
    @pi18n
    def GET(self, *args):
        images = web.ctx.orm.query(Image).all()
        return web.ctx.render.plugins.disk_image.adminimages(images)


class EditImage:
    @pi18n
    def GET(self, type, value):
        orm = web.ctx.orm
        image = None
        if type == 'name':
            image = orm.query(Image).filter(Image.name == value).first()

        elif type == 'admin':
            image = orm.query(Image).filter(Image.id == value).first()

        else:
            raise web.notfound()

        if not image:
            raise web.notfound()

        return web.render.plugins.disk_image.edit_image(image)


class NewDiskProfile:
    @pi18n
    def GET(self):
        orm = web.ctx.orm
        lt = time.localtime()
        string = _('profile')
        name = ('%s_%d/%d/%d_%d:%d:%d' % (string, lt.tm_year, lt.tm_mon,
                lt.tm_mday, lt.tm_hour, lt.tm_min, lt.tm_sec))

        '''
        if orm.query(DiskProfile).filter(DiskProfile.name == name).first():
            for n in range(1, 10):
                pos_name = ('%s-%d' % (name, n))
                if not orm.query(DiskProfile).filter(DiskProfile.name ==
                        pos_name).first():
                    name = pos_name
                    break
        '''

        diskprofile = DiskProfile(name)
        disk = diskprofile.add_empty_disk()
        disk.set_cache(20)

        orm.add(diskprofile)
        orm.commit()

        image_plugin = web.ctx.plugin_manager.get_plugin_by_name('disk_image')
        raise web.seeother(image_plugin.fill_url(DiskProfileView, profile.id))


class EditDiskProfile:
    @pi18n
    def GET(self, profile_id):
        orm = web.ctx.orm
        profile = orm.query(DiskProfile).filter(DiskProfile.id ==
                profile_id).first()

        if not profile:
            raise web.notfound()

        return web.ctx.render.plugins.disk_image.editdiskprofile(profile,
                FormCreator())

    def POST(self, profile_id):
        orm = web.ctx.orm
        profile = orm.query(DiskProfile).filter(DiskProfile.id ==
                profile_id).first()

        if not profile:
            raise web.notfound()

        user_data = web.input(command="")
        command = user_data['command']

        if command == 'remove_partition':
            disk = profile.get_disk(int(user_data['disk']))
            disk.remove_partition(int(user_data['partition']))

        elif command == 'add_partition':
            disk = profile.get_disk(int(user_data['disk']))
            primary = (user_data['type'] == 'Primary')
            begining = (user_data['position'] == 'Begining')
            disk.add_partition(size=Decimal(user_data['size']),
                    primary=primary, begining=begining,
                    filesystem=user_data['filesystem'])

        elif command == 'remove_disk':
            profile.remove_disk(int(user_data['disk']))

        elif command == 'add_disk':
            profile.add_empty_disk()

        elif command == 'profile':
            profile.name = user_data['name']
            profile.description = user_data['description']
            profile.set_cache(Decimal(user_data['cache']))

        orm.commit()

        return self.GET(profile_id)


class FormCreator:
    @pi18n
    def size_validator(self):
        return [form.regexp(r"\d?\d(.\d\d?)?",
                _("Must be a number. You can use two decimals")),
                form.Validator(_("Must be higher than 0 and less than 50"),
                lambda x:float(x) < 50 and float(x) > 0)]

    def disk_profile_form(self, profile):
        cache_size = 0
        cache = profile.get_cache()
        if cache:
            cache_size = cache.size

        return form.Form(
                form.Textbox('name',
                    form.notnull,
                    maxlenght=256,
                    description=_("Name"),
                    value=profile.name),
                form.Textarea('description',
                    description=_("Description"),
                    maxlenght=4096,
                    value=profile.description),
                form.Textbox('cache',
                    self.size_validator(),
                    maxlenght=5,
                    description=_("Cache Size"),
                    value=cache_size),
                form.Hidden(name='command', value='profile'),
                form.Button(_('Apply'), type='submit',
                    description=_("Apply changes")))
                    # value=profile.get_cache().size))

    @pi18n
    def remove_partition_form(self, partition):
        return form.Form(
                form.Hidden(name='command', value='remove_partition'),
                form.Hidden(name='disk', value=partition.disk.number),
                form.Hidden(name='partition', value=partition.number),
                form.Button(_('Remove'), type='submit',
                    description=_("Remove the partition")))


    @pi18n
    def add_partition_form(self, disk):
        primary_free_space = disk.can_add_primary_partition()
        logic_free_space = disk.can_add_logic_partition()

        if not primary_free_space and not logic_free_space:
            return None

        dropdown_args = []
        default_value = None
        size_value = 0

        if logic_free_space:
            dropdown_args.append(_("Logic"))
            default_value = _("Logic")
            size_value = logic_free_space

        if primary_free_space:
            dropdown_args.append(_("Primary"))
            default_value = _("Primary")
            size_value = logic_free_space


        return form.Form(
                form.Dropdown(name="type", args=dropdown_args, value=default_value),
                form.Dropdown(name="position", args=[_("Begining"),_("End")],
                    value=_("Begining")),
                form.Textbox("size", self.size_validator(),
                    description="Size", value=size_value),
                # form.Dropdown(name="image", args=get_repo().images, value=),
                form.Dropdown(name="filesystem", description="FileSystem",
                    args=Partition.filesystems),
                form.Hidden(name='command', value='add_partition'),
                form.Hidden(name='disk', value=disk.number),
                form.Button(_("Add"), type="submit",
                    description=_("Add partition")))

    @pi18n
    def remove_disk_form(self, disk):
        # The first disk can't be removed
        if disk.number == 1:
            return None
        return form.Form(
                form.Hidden(name='command', value='remove_disk'),
                form.Hidden(name='disk', value=disk.number),
                form.Button(_('Remove Disk'), type='submit',
                    description=_('Remove disk from profile')))

    @pi18n
    def add_disk_form(self):
        return form.Form(
                form.Hidden(name='command', value='add_disk'),
                form.Button(_('Add Disk'), type='submit',
                    decription=_('Add a new disk')))


class AssignDiskProfile(DPView):

    def delegate(self, method, arg, name):
        try:
            action = getattr(self, "%s_%s" % (method.lower(), arg))
            return action(name)
        except AttributeError:
            raise web.notfound()

    def GET(self, arg, name):
        return self.delegate('GET', arg, name)

    def POST(self, arg, name):
        return self.delegate('POST', arg, name)

    def get_or_404(self, model, name):
        orm = web.ctx.orm
        q = orm.query(model).filter_by(name=name)
        if not q.count():
            raise web.notfound()
        q = q.one()
        return q

    def get_ou(self, name):
        ou = self.get_or_404(OrganizativeUnit, name)
        return self.render.assign(get_user(), self.get_form(ou=ou), ou=ou)

    def post_ou(self, name):
        ou = self.get_or_404(OrganizativeUnit, name)
        f = self.get_form(ou=ou)
        orm = web.ctx.orm
        if f.validates():
            v = f.dp.value
            if ou.disk_profile:
                ou.disk_profile.remove(ou.disk_profile[0])
            if v != '--':
                dp = orm.query(DiskProfile).filter_by(name=v).one()
                dp.ous.append(ou)
        else:
            return self.render.assign(get_user(), f, ou=ou)

        raise web.seeother('/navigator/ou/%s/' % name)

    def get_computer(self, name):
        comp = self.get_or_404(Computer, name)
        return self.render.assign(get_user(), self.get_form(computer=comp), computer=comp)

    def post_computer(self, name):
        comp = self.get_or_404(Computer, name)
        f = self.get_form(computer=comp)
        orm = web.ctx.orm
        if f.validates():
            v = f.dp.value
            if comp.disk_profile:
                comp.disk_profile.remove(comp.disk_profile[0])
            if v != '--':
                dp = orm.query(DiskProfile).filter_by(name=v).one()
                dp.computers.append(comp)
        else:
            return self.render.assign(get_user(), f, computer=comp)

        raise web.seeother('/navigator/computer/%s/' % name)

    @pi18n
    def get_form(self, ou=None, computer=None):
        orm = web.ctx.orm()
        q = orm.query(DiskProfile).all()
        args = [i.name for i in q]
        args = ['--'] + args
        val = '--'
        if ou:
            q1 = orm.query(DiskProfile).filter(DiskProfile.ous.contains(ou))
            if q1.count():
                val = q1.first().name
        if computer:
            q1 = orm.query(DiskProfile).filter(DiskProfile.computers.contains(computer))
            if q1.count():
                val = q1.first().name

        f = form.Form(
                form.Dropdown(name="dp", args=args, value=val,
                              description=_("Disk Profile")),
            )
        return f


class RemoveDiskProfile(DPView):

    def GET(self, dp_id):
        try:
            dp = self.orm.query(DiskProfile).get(dp_id)
        except:
            raise web.notfound()

        return self.render.removedp(dp)

    @pi18n
    def POST(self, dp_id):
        try:
            dp = self.orm.query(DiskProfile).get(dp_id)
        except:
            raise web.notfound()

        dpname = dp.name

        self.orm.delete(dp)

        web.ctx.notify(_("Disk profile '%s' removed correctly" % dpname), "notification")

        image_plugin = web.ctx.plugin_manager.get_plugin_by_name('disk_image')
        raise web.seeother(image_plugin.fill_url(DiskProfileListView))


class RestoreDiskProfile(DPView):
    def get_or_404(self, entity, entityname):
        model = Computer
        if entity == 'ou':
            model = OrganizativeUnit

        entityobj = self.orm.query(model).filter_by(name=entityname).first()
        if not entityobj or not entityobj.disk_profile:
            raise web.notfound()

        dp = entityobj.disk_profile[0]
        return entityobj, dp

    @pi18n
    def GET(self, entity, entityname):
        entityobj, dp = self.get_or_404(entity, entityname)

        return self.render.restoredp(dp, entity, entityobj)

    @pi18n
    def POST(self, entity, entityname):
        entityobj, dp = self.get_or_404(entity, entityname)

        job = RestoreDiskProfileJob(entity, entityobj.id)
        job.send()

        raise web.seeother('/jobs/%s' % job.id)
