# ===========================================================================
#  This file is part of the Flyscan Ecosystem
#
#  Copyright 2014-EOT Synchrotron SOLEIL, St.Aubin, France
#
#  This is free software: you can redistribute it and/or modify it under the
#  terms of the GNU Lesser General Public License as published by the Free
#  Software Foundation, either version 3 of the License, or (at your option)
#  any later version.
#
#  This is distributed in the hope that it will be useful, but WITHOUT ANY
#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
#  FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
#  more details.
#
#  You should have received a copy of the GNU Lesser General Public License
#  along with This.  If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================

"""A ftp client Tango device (with Flyscan specific behaviour)"""

from six import add_metaclass

import os

from PyTango import DevState, DevFailed

from fs.utils.tango import run, Device, DeviceMeta, attribute, command, device_property
from fs.utils.logging import tracer, DeviceLogAdapter
from fs.utils.errors import GenericError
from fs.ftpclient.FtpClientImpl import FtpClientTask, FtpClientConfig, FtpClientPerf


# ===========================================================================
@add_metaclass(DeviceMeta)
class FtpClient(Device, DeviceLogAdapter):

    #__metaclass__ = DeviceMeta

    # device properties
    FTPServerAddress = device_property(dtype=str)
    FTPServerPort = device_property(dtype=int)
    FTPServerDirectory = device_property(dtype=str)
    FilesNamePattern = device_property(dtype=str)
    FilesPollingPeriod = device_property(dtype=str)
    FilesDestinationDirectory = device_property(dtype=str)
    LowWaterMark = device_property(dtype=int)
    HighWaterMark = device_property(dtype=int)

    # --------------------------------------------------------------------------
    def __init__(self, cl, name):
        # mother-classes
        Device.__init__(self, cl, name)
        DeviceLogAdapter.__init__(self, self)

    # --------------------------------------------------------------------------
    @tracer
    def init_device(self):
        # pytango impl.: __init__ hasn't already been called!
        self.host_device = self
        # members initialization
        self.__core_impl = None
        self.__perf = FtpClientPerf()
        self.__properly_initialized = False
        self.__status = "device initialization failed"
        # ----------------------------------------------------------------
        try:
            # get device properties from tango db
            self.get_device_properties()
            # configuration
            cfg = FtpClientConfig()
            cfg.host_tango_device = self
            # check properties values
            if self.FTPServerAddress is None:
                raise Exception("Device initialization failed: 'FTPServerAddress' property is missing and can't be guessed!")
            cfg.host = self.FTPServerAddress
            if self.FTPServerPort is not None:
                cfg.port = self.FTPServerPort
            if self.FilesNamePattern is not None:
                cfg.watch_pattern = self.FilesNamePattern
            if self.FilesPollingPeriod is not None:
                cfg.watch_period = float(self.FilesPollingPeriod)
            cfg.watched_directory = self.FTPServerDirectory
            if self.LowWaterMark is not None:
                cfg.lo_water_mark = self.LowWaterMark
            if self.HighWaterMark is not None:
                cfg.hi_water_mark = self.HighWaterMark
            if self.FilesDestinationDirectory is None:
                raise Exception("'FilesDestinationDirectory' property is missing and can't be guessed!")
            try:
                os.makedirs(self.FilesDestinationDirectory)
            except OSError as err:
                if err.errno != 17:  # already exist
                    raise
                if not os.access(self.FilesDestinationDirectory, os.W_OK):
                    raise Exception("'FilesDestinationDirectory' property is invalid: write permission required")
            cfg.destination_directory = self.FilesDestinationDirectory
            # instantiate/initialize core implementation
            self.__core_impl = FtpClientTask(cfg)
            self.__core_impl.start()
            # initialization successful
            self.__properly_initialized = True
        except DevFailed as e:
            self.error("Tango exception caught in FtpClient.init_device:\n%s" % e)
            self.__status = "device switched to FAULT on following error:\n%s" % e
        except Exception:
            ge = GenericError()
            self.error("exception caught in FtpClient.init_device:\n%s" % ge.message)
            self.__status = "device switched to FAULT on following error:\n%s" % ge.message

    # --------------------------------------------------------------------------
    @tracer
    def delete_device(self):
        try:
            if self.__core_impl is not None:
                self.__core_impl.exit()
        except Exception:
            self.error(GenericError())
        self.__properly_initialized = False

    # --------------------------------------------------------------------------
    def dev_state(self):
        if not self.__properly_initialized:
            return DevState.FAULT
        return self.__core_impl.state

    # --------------------------------------------------------------------------
    def dev_status(self):
        if self.__properly_initialized:
            if self.__core_impl is not None:
                self.__status = self.__core_impl.status
            else:
                self.__status = "unexpected null ref. to FtpClientTask!"
        return self.__status

    # --------------------------------------------------------------------------
    def read_attr_hardware(self, attr_list):
        del attr_list  # unused
        if self.__core_impl is not None:
            self.__perf = self.__core_impl.performances
        else:
            self.__perf = FtpClientPerf()

    # --------------------------------------------------------------------------
    @attribute(dtype=str)
    def transferredFile(self):
        return self.__perf.file_name

    # --------------------------------------------------------------------------
    @attribute(dtype=float, unit="MB")
    def transferredFileSize(self):
        return self.__perf.file_size

    # --------------------------------------------------------------------------
    @attribute(dtype=float, unit="s")
    def transferredFileTime(self):
        return self.__perf.file_transfer_time

    # --------------------------------------------------------------------------
    @attribute(dtype=float, unit="MB/s")
    def transferredFileRate(self):
        return self.__perf.file_transfer_rate

    # --------------------------------------------------------------------------
    @attribute(dtype=float, unit="MB/s")
    def currentTransferRate(self):
        return self.__perf.current_transfer_rate

    # --------------------------------------------------------------------------
    @command
    def DeleteRemainingFiles(self):
        if self.__core_impl is not None:
            self.__core_impl.delete_remaining_files()

    # --------------------------------------------------------------------------
    @command
    def Start(self):
        if self.__core_impl is not None:
            self.__core_impl.start_files_transfer()

    # --------------------------------------------------------------------------
    @command
    def Stop(self):
        if self.__core_impl is not None:
            self.__core_impl.stop_files_transfer()

    # --------------------------------------------------------------------------
    @command
    def Abort(self):
        if self.__core_impl is not None:
            self.__core_impl.abort()

if __name__ == "__main__":
    run((FtpClient,))
