#!/usr/bin/env python
#
# Copyright 2013 Simone Campagna
#
# 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.
#

__author__ = 'Simone Campagna'

import os
import abc
import shutil

from ..field import Field, BasePathVisitor
from ..subfile import BaseSubFile
from ..path import File
from .debug import LOGGER
from .flag import Flag

def _dry_run_log(dry_run, text, log=None, log_dry=None):
    if log is None:
        log = LOGGER.info
    if log_dry is None:
        log_dry = LOGGER.warning
    if dry_run:
        dry_run_message = "[dry-run] "
        log = log_dry
    else:
        dry_run_message = ""
    log("{}{}".format(dry_run_message, text))

class _BaseFileOperator(object):
    __metaclass__ = abc.ABCMeta
    __op_name__ = None
    def __init__(self,
                    dry_run=False,
                    depth=None,
                    recur_subfiles=None,
                    recur_files=None):
        self.dry_run = dry_run
        if depth is None:
            depth = 1
        self.depth = depth
        if recur_subfiles is None:
            recur_subfiles = True
        self.recur_subfiles = recur_subfiles
        if recur_files is None:
            recur_files = False
        self.recur_files = recur_files
        self._modified_fields = []

    @abc.abstractmethod
    def operate(self, field_type, field_path):
        return field_type.createfromfile(field_path)

    def _add_modified_field(self, field, *field_data):
        self._modified_fields.append((field, ) + field_data)

    def _reset_modified_fields(self):
        self._modified_fields = []

    def modified_fields(self):
        return iter(self._modified_fields)

    @abc.abstractmethod
    def visit(self, field, level=0):
        if isinstance(field, File):
            if isinstance(field, BaseSubFile):
                if not self.recur_subfiles:
                    return False
            else:
                if not self.recur_files:
                    return False
            return True
        else:
            return False

class _FileOperator(_BaseFileOperator):
    def visit(self, field):
        must_visit = super(_FileOperator, self).visit(field)
        if not must_visit:
            return
        field_path = field.value
        self._add_modified_field(field, field_path)
        
    def operate(self, field_type, field_path):
        field = super(_FileOperator, self).operate(field_type, field_path)
        self._recursive_operate(field, field_path, self.depth)

    def _recursive_operate(self, field, input_path, depth):
        if depth - 1:
            self._reset_modified_fields()
            field.accept(self)
            for subfile, subfile_path in self.modified_fields():
                if os.path.lexists(subfile_path):
                    if isinstance(subfile, BaseSubFile):
                        subfield = subfile.__subtype__.createfromfile(subfile_path)
                        self._recursive_operate(
                            field=subfield,
                            input_path=subfile_path,
                            depth=depth - 1,
                        )
                    else:
                        _dry_run_log(self.dry_run, "{} file {}".format(self.__op_name__, subfile_path))
                        if not self.dry_run:
                            self._operate_on_file(subfile_path)
        if depth:
            _dry_run_log(self.dry_run, "{}: {} {}".format(field.name, self.__op_name__, input_path))
            if not self.dry_run:
                self._operate_on_field(field, input_path)
                
            
    @abc.abstractmethod
    def _operate_on_file(self, input_path):
        pass

    @abc.abstractmethod
    def _operate_on_field(self, field, input_path):
        pass

class _FileOutputOperator(_BaseFileOperator):
    def __init__(self,
                    output_path,
                    dry_run=False,
                    depth=1,
                    overwrite=False,
                    recur_files=None,
                    recur_subfiles=None,
                    prefix=None,
                    suffix=None,
                    subdir=None,
                    preserve_subdirs=None):
        super(_FileOutputOperator, self).__init__(
            dry_run=dry_run,
            depth=depth,
            recur_files=recur_files,
            recur_subfiles=recur_subfiles,
        )
        self.output_path = output_path
        if not isinstance(overwrite, Flag):
            overwrite = Flag(overwrite, "overwrite")
        self.overwrite = overwrite
        if prefix is None:
            prefix = ''
        self.prefix = prefix
        if suffix is None:
            suffix = ''
        self.suffix = suffix
        if subdir is None:
            subdir = os.path.dirname(self.output_path)
        self.subdir = subdir
        if preserve_subdirs is None:
            preserve_subdirs = False
        self.preserve_subdirs = preserve_subdirs

    def get_output_path(self, base_dir, field_path):
        field_dir, field_file = os.path.split(field_path)
        new_path = self.subdir
        if self.preserve_subdirs:
            subdir = os.path.relpath(field_dir, base_dir)
            new_path = os.path.join(new_path, subdir)
        filename = self.prefix + field_file + self.suffix
        new_path = os.path.normpath(os.path.join(new_path, filename))
        return new_path

    def visit(self, field):
        must_visit = super(_FileOutputOperator, self).visit(field)
        if not must_visit:
            return
        if field._parent:
            base_dir = field._parent.base_path
        else:
            base_dir = '.'
        new_path = self.get_output_path(base_dir, field.value)
        _dry_run_log(self.dry_run, "{}: change {} -> {}".format(field.name, field.value, new_path))
        if os.path.exists(new_path) and not self.overwrite.verify("Overwrite {} -> {}".format(field.value, new_path)):
            _dry_run_log(self.dry_run, "{}: cannot overwrite existing file {}".format(field.name, new_path), log=LOGGER.error, log_dry=LOGGER.error)
            if not self.dry_run:
                raise IOError("cannot overwrite file {}".format(new_path))
        old_path = field.value
        if not self.dry_run:
            field.value = new_path
        self._add_modified_field(field, old_path, new_path)
        
    def operate(self, field_type, field_path):
        field = super(_FileOutputOperator, self).operate(field_type, field_path)
        self._recursive_operate(field, field_path, self.output_path, self.depth)

    def _recursive_operate(self, field, input_path, output_path, depth):
        if depth - 1:
            self._reset_modified_fields()
            field.accept(self)
            for subfile, old_path, new_path in self.modified_fields():
                if os.path.lexists(old_path):
                    if isinstance(subfile, BaseSubFile):
                        subfield = subfile.__subtype__.createfromfile(old_path)
                        self._recursive_operate(
                            field=subfield,
                            input_path=old_path,
                            output_path=new_path,
                            depth=depth - 1,
                        )
                    else:
                        _dry_run_log(self.dry_run, "{} file {} -> {}".format(self.__op_name__, old_path, new_path))
                        if not self.dry_run:
                            self._operate_on_file(old_path, new_path)
        if depth:
            _dry_run_log(self.dry_run, "{}: {} {} -> {}".format(field.name, self.__op_name__, input_path, output_path))
            if not self.dry_run:
                self._operate_on_field(field, input_path, output_path)
                
            
    @abc.abstractmethod
    def _operate_on_file(self, input_path, output_path):
        pass

    @abc.abstractmethod
    def _operate_on_field(self, field, input_path, output_path):
        pass

class FileCopy(_FileOutputOperator):
    __op_name__ = 'copy'
    def _operate_on_file(self, input_path, output_path):
        shutil.copy(input_path, output_path)
        
    def _operate_on_field(self, field, input_path, output_path):
        field = field.clone()
        field.tofile(output_path)

class FileMove(FileCopy):
    __op_name__ = 'move'
    def _operate_on_file(self, input_path, output_path):
        os.rename(input_path, output_path)
        
    def _operate_on_field(self, field, input_path, output_path):
        super(FileMove, self)._operate_on_field(field, input_path, output_path)
        os.remove(input_path)

class FileRemove(_FileOperator):
    __op_name__ = 'remove'
    def _operate_on_file(self, input_path):
        os.remove(input_path)
        
    def _operate_on_field(self, field, input_path):
        os.remove(input_path)
    

            
