# Copyright (c) 2015 The New Mexico Consortium
#
# {{{NMC-LICENSE
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
#
# }}}

# To ensure that dividing two integers results in a floating point number
from __future__ import division

# nmc_probe imports
from nmc_probe.target_manager import TargetManager
from nmc_probe.zfs import ZFS
from nmc_probe.log import Log

# other imports
from sqlalchemy_utils import JSONType, ChoiceType, Timestamp
from flask_sqlalchemy import SQLAlchemy
import uuid
import datetime

db = SQLAlchemy()


class LUNCloneJob(db.Model, Timestamp):
    status_types = [('pending',                'pending'),
                    ('start_job',              'start_job'),
                    ('start_creating_clones',  'start_creating_clones'),
                    ('done_creating_clones',   'done_creating_clones'),
                    ('start_deleting_clones',  'start_deleting_clones'),
                    ('done_deleting_clones',   'done_deleting_clones'),
                    ('start_waiting_udevd',    'start_waiting_udevd'),
                    ('done_waiting_udevd',     'done_waiting_udevd'),
                    ('start_creating_targets', 'start_creating_targets'),
                    ('done_creating_targets',  'done_creating_targets'),
                    ('start_deleting_targets', 'start_deleting_targets'),
                    ('done_deleting_targets',  'done_deleting_targets'),
                    ('complete',               'complete'),
                    ('failed',                 'failed')]

    job_types = [('create', 'create'),
                 ('delete', 'delete')]

    __tablename__     = 'lun_clone_job'
    id                = db.Column(db.String(20), primary_key=True)
    details           = db.Column(JSONType)
    status            = db.Column(ChoiceType(status_types), index=True)
    job_type          = db.Column(ChoiceType(job_types), index=True)
    num_requested     = db.Column(db.Integer, default=0)
    num_clones        = db.Column(db.Integer, default=0)
    num_targets       = db.Column(db.Integer, default=0)
    num_completed     = db.Column(db.Integer, default=0)
    percent_completed = db.Column(db.Float, default=0)
    error             = db.Column(db.Unicode(2048))
    job_start         = db.Column(db.DateTime)
    job_done          = db.Column(db.DateTime)
    job_time          = db.Column(db.Float, default=0)
    clone_start       = db.Column(db.DateTime)
    clone_done        = db.Column(db.DateTime)
    clone_time        = db.Column(db.Float, default=0)
    target_start      = db.Column(db.DateTime)
    target_done       = db.Column(db.DateTime)
    target_time       = db.Column(db.Float, default=0)
    udevd_start       = db.Column(db.DateTime)
    udevd_done        = db.Column(db.DateTime)
    udevd_time        = db.Column(db.Float, default=0)
    lag_time          = db.Column(db.Float, default=0)
    aggregated        = db.Column(db.Boolean, default=False, index=True)

    def update_percent_completed(self):
        '''Update the percent complete based on the number of clones
        and targets that have been created'''
        self.percent_completed = (self.num_clones + self.num_targets) * \
            0.5 / self.num_requested
        db.session.commit()

    def increment_num_clones(self):
        '''Increment the number of clones that have been created or deleted'''
        self.num_clones = self.num_clones + 1
        self.update_percent_completed()
        db.session.commit()

    def increment_num_targets(self):
        '''Increment the number of targets that have been created or deleted'''
        self.num_targets = self.num_targets + 1
        self.update_percent_completed()
        db.session.commit()

    def increment_num_completed(self):
        '''Increment the number of completed requests for this job'''
        self.num_completed = self.num_completed + 1
        db.session.commit()

    def set_status_start_creating_clones(self):
        '''Set the status to creating clones'''
        self.start_clone = datetime.datetime.now()
        self.set_status(u'start_creating_clones')

    def set_status_done_creating_clones(self):
        '''Set the status to creating clones'''
        self.done_clone = datetime.datetime.now()
        self.clone_time = (self.done_clone - self.start_clone).total_seconds()
        self.set_status(u'done_creating_clones')

    def set_status_start_deleting_clones(self):
        '''Set the status to deleting clones'''
        self.start_clone = datetime.datetime.now()
        self.set_status(u'start_deleting_clones')

    def set_status_done_deleting_clones(self):
        '''Set the status to deleting clones'''
        self.done_clone = datetime.datetime.now()
        self.clone_time = (self.done_clone - self.start_clone).total_seconds()
        self.set_status(u'done_deleting_clones')

    def set_status_start_creating_targets(self):
        '''Set the status to creating targets'''
        self.start_target = datetime.datetime.now()
        self.set_status(u'start_creating_targets')

    def set_status_done_creating_targets(self):
        '''Set the status to creating targets'''
        self.done_target = datetime.datetime.now()
        self.target_time = (self.done_target - self.start_target)
        self.target_time = self.target_time.total_seconds()
        self.set_status(u'done_creating_targets')

    def set_status_start_deleting_targets(self):
        '''Set the status to deleting targets'''
        self.start_target = datetime.datetime.now()
        self.set_status(u'start_deleting_targets')

    def set_status_done_deleting_targets(self):
        '''Set the status to deleting targets'''
        self.done_target = datetime.datetime.now()
        self.target_time = (self.done_target - self.start_target)
        self.target_time = self.target_time.total_seconds()
        self.set_status(u'done_deleting_targets')

    def set_status_start_waiting_udevd(self):
        '''Set the status to creating targets'''
        self.start_udevd = datetime.datetime.now()
        self.set_status(u'start_waiting_udevd')

    def set_status_done_waiting_udevd(self):
        '''Set the status to creating targets'''
        self.done_udevd = datetime.datetime.now()
        self.udevd_time = (self.done_udevd - self.start_udevd).total_seconds()
        self.set_status(u'done_waiting_udevd')

    def set_status_start_job(self):
        self.job_start = datetime.datetime.now()
        self.lag_time = (self.job_start - self.created).total_seconds()
        self.set_status(u'start_job')

    def set_job_time(self):
        self.done_job = datetime.datetime.now()
        self.job_time = (self.done_job - self.job_start).total_seconds()

    def set_status_complete(self):
        '''Set the status to complete'''
        self.set_job_time()
        self.set_status(u'complete')

    def set_status_failed(self, error):
        '''Set the status to failed'''
        self.set_job_time()
        self.error = error
        self.set_status(u'failed')

    def set_status(self, status):
        self.status = status
        db.session.commit()

    def is_create(self):
        return self.job_type.code == 'create'

    @classmethod
    def get_job(cls, id):
        '''Get the status for the specified job id'''
        # print id
        results = db.session.query(cls).filter(cls.id == id)
        # print id
        return results

    @classmethod
    def with_args(cls, args, job_type_name):
        guid = '%s' % uuid.uuid4()
        details = cls(id=guid,
                      details=args,
                      status=u'pending',
                      job_type=job_type_name,
                      num_requested=len(args['clones']),
                      num_clones=0,
                      num_targets=0,
                      num_completed=0,
                      percent_completed=0.0)
        db.session.add(details)
        db.session.commit()
        return details

    @classmethod
    def repeat(cls, orig):
        guid = '%s' % uuid.uuid4()
        job = cls(id=guid,
                  details=orig.details,
                  status=u'pending',
                  job_type=orig.job_type,
                  num_requested=orig.num_requested,
                  num_clones=0,
                  num_targets=0,
                  num_completed=0,
                  percent_completed=0.0)
        db.session.add(job)
        db.session.commit()
        return job

# 13e82c8b-7d34-46aa-8de4-c7aef4ead843

    @classmethod
    def pending(cls):
        '''Return all pending jobs, ordered by the created timestamp'''
        return db.session.query(cls).filter(cls.status == u'pending').order_by(cls.created)
