# -*- coding: utf-8 -*-
'''
    vdirsyncer.storage.singlefile
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    :copyright: (c) 2014 Markus Unterwaditzer & contributors
    :license: MIT, see LICENSE for more details.
'''

import collections

from .base import Item, Storage
import vdirsyncer.exceptions as exceptions
import vdirsyncer.log as log
from vdirsyncer.utils import expand_path, safe_write, itervalues, checkfile
from vdirsyncer.utils.vobject import split_collection, join_collection

logger = log.get(__name__)


class SingleFileStorage(Storage):
    '''Save data in single VCALENDAR file, like Orage -- a calendar app for
    XFCE -- and Radicale do. Usable as ``singlefile`` in the config file.

    This storage has many raceconditions and is very slow. What this basically
    means is that you shouldn't use this storage unless you have to (e.g. you
    use a calendar client which requires it)'''

    _repr_attributes = ('path',)

    _write_mode = 'wb'
    _append_mode = 'ab'
    _read_mode = 'rb'

    _items = None

    def __init__(self, path, wrapper=None, encoding='utf-8', create=True,
                 collection=None, **kwargs):
        super(SingleFileStorage, self).__init__(**kwargs)
        path = expand_path(path)

        if collection is not None:
            raise ValueError('collection is not a valid argument for {}'
                             .format(type(self).__name__))

        checkfile(path, create=create)

        if create:
            self._write_mode = 'wb+'
            self._append_mode = 'ab+'

        self.path = path
        self.encoding = encoding
        self.create = create
        self.wrapper = wrapper

    def list(self):
        self._items = collections.OrderedDict()

        try:
            with open(self.path, self._read_mode) as f:
                text = f.read().decode(self.encoding)
        except IOError as e:
            import errno
            if e.errno != errno.ENOENT or not self.create:  # file not found
                raise
            return ()

        rv = []
        for item in split_collection(text):
            item = Item(item)
            href = self._get_href(item)
            etag = item.hash
            self._items[href] = item, etag
            rv.append((href, etag))

        # we can't use yield here because we need to populate our
        # dict even if the user doesn't exhaust the iterator
        return rv

    def get(self, href):
        if self._items is None:
            self.list()

        try:
            return self._items[href]
        except KeyError:
            raise exceptions.NotFoundError(href)

    def upload(self, item):
        href = self._get_href(item)
        self.list()
        if href in self._items:
            raise exceptions.AlreadyExistingError(href)

        self._items[href] = item, item.hash
        self._write()
        return href, item.hash

    def update(self, href, item, etag):
        self.list()
        if href not in self._items:
            raise exceptions.NotFoundError(href)

        _, actual_etag = self._items[href]
        if etag != actual_etag:
            raise exceptions.WrongEtagError(etag, actual_etag)

        self._items[href] = item, item.hash
        self._write()
        return item.hash

    def delete(self, href, etag):
        self.list()
        if href not in self._items:
            raise exceptions.NotFoundError(href)

        _, actual_etag = self._items[href]
        if etag != actual_etag:
            raise exceptions.WrongEtagError(etag, actual_etag)

        del self._items[href]
        self._write()

    def _write(self):
        text = join_collection(
            (item.raw for item, etag in itervalues(self._items)),
            wrapper=self.wrapper
        )
        try:
            with safe_write(self.path, self._write_mode) as f:
                f.write(text.encode(self.encoding))
        finally:
            self._items = None

@@ -691,16 +691,34 @@
  file.%0A%0A
+    .. note::%0A    
     This
@@ -777,16 +777,24 @@
 hat this
+%0A       
  basical
@@ -795,20 +795,16 @@
 asically
-%0A   
  means i
@@ -856,16 +856,24 @@
 you have
+%0A       
  to (e.g
@@ -877,20 +877,16 @@
 e.g. you
-%0A   
  use a c
@@ -918,16 +918,447 @@
 ires it)
+%0A%0A    Example for syncing with :py:class:%60vdirsyncer.storage.CaldavStorage%60::%0A%0A        %5Bpair my_calendar%5D%0A        a = my_calendar_local%0A        b = my_calendar_remote%0A%0A        %5Bstorage my_calendar_local%5D%0A        type = singlefile%0A        path = ~/my_calendar.ics%0A%0A        %5Bstorage my_calendar_remote%5D%0A        type = caldav%0A        url = https://caldav.example.org/username/my_calendar/%0A        #username =%0A        #password =%0A%0A    
 '''%0A%0A   

Fix #66