import os

from django.core.files.base import File
from django.core.files.storage import default_storage
from django.core.serializers.json import DjangoJSONEncoder


class StreamFieldDataEncoder(DjangoJSONEncoder):
    def default(self, o):
        if isinstance(o, StreamFieldFile):
            return {
                'name': o.name,
                'filename': o.filename,
            }
        return super().default(o)


class StreamFieldFile(File):
    def __init__(self, *args, filename=None, storage=default_storage, **kwargs):
        super().__init__(*args, **kwargs)
        self.storage = storage
        self.filename = filename or os.path.basename(self.name)
        self._committed = False

    def __str__(self):
        return self.filename

    def __eq__(self, other):
        if isinstance(other, File):
            return self.filename == other.filename and self.size == other.size
        # Rely on the other object to know how to check equality
        # Could cause infinite loop if the other object is unsure how to compare
        return other.__eq__(self)

    def _get_file(self):
        if getattr(self, '_file', None) is None:
            self._file = self.storage.open(self.name, 'rb')
        return self._file

    def _set_file(self, file):
        self._file = file

    def _del_file(self):
        del self._file

    file = property(_get_file, _set_file, _del_file)

    def read(self, chunk_size=None):
        self.file.seek(0)
        if chunk_size:
            return super().read(chunk_size)
        else:
            return super().read()

    @property
    def path(self):
        return self.storage.path(self.name)

    @property
    def url(self):
        return self.storage.url(self.name)

    @property
    def size(self):
        if not self._committed:
            return self.file.size
        return self.storage.size(self.name)

    def open(self, mode='rb'):
        if getattr(self, '_file', None) is None:
            self.file = self.storage.open(self.name, mode)
        else:
            self.file.open(mode)
        return self

    def save(self, folder):
        name = self.name
        if not name.startswith(folder):
            name = os.path.join(folder, name)
        name = self.storage.generate_filename(name)
        if not self._committed:
            self.name = self.storage.save(name, self.file)
        self._committed = True

    def delete(self, save=True):
        if not self:
            return
        # Only close the file if it's already open, which we know by the
        # presence of self._file
        if hasattr(self, '_file'):
            self.close()
            del self.file

        self.storage.delete(self.name)

        self.name = None
        self._committed = False

    @property
    def closed(self):
        file = getattr(self, '_file', None)
        return file is None or file.closed

    def close(self):
        file = getattr(self, '_file', None)
        if file is not None:
            file.close()