#!/bin/python

from kivy.app import App
from kivy.clock import Clock
from kivy.event import EventDispatcher
from kivy.properties import BooleanProperty, NumericProperty, ListProperty, ObjectProperty, \
    DictProperty, StringProperty, ObservableList, ObservableDict

from functools import partial

''' The Model is an EventDispatcher that keeps track of what needs to be
saved and unpacks its properties for storage in the ModelStore.
Reference implementation is file-backed JSON.

You can bind on all the properties as usual and the data will be
persisted when you either call save or you can use autosave or
save_on_stop.  Other saving behavior can be designed by calling save
manually. 

The ModelProperty is built to allow nesting.  Models will build their
ModelProperties when instantiated and will save their child Models when
saved.  This can be customized with save_children.

Currently there is no support for business logic or any transaction
manager to facilitate rollbacks that would be useful for network stores
such as networked clients in a multi-user environment.

Transaction support would be nice for both local and network changes in
the case that the application is trying to save an invalid model.
Validation of model data is desireable in both directions to avoid
loading the application in an invalid internal state. '''


class ModelProperty(ObjectProperty):
    ''' An Object property that holds a model Model instance.  This model
    allows Models to become nested in trees that share saving and copying
    methods'''
    def __init__(self, defaultvalue=None, **kw):
        super(ModelProperty, self).__init__(defaultvalue, baseclass=Model, **kw)


class Model(EventDispatcher):
    ''' Subclass to create domain objects that can be persisted in a
    Modelstore.'''
        
    def __init__(self, key, save_on_stop=False, auto_save=False,
                 save_children=True, default=None, **kwargs):
        super(Model, self).__init__(**kwargs)
        self._key = key
        self._default = default

        # thought about using some meta magic to make the api for
        # setting defaults not require overriding __init__, but it
        # started looking stupid and too opaque.
        self._save_children = save_children

        self._needs_update = set() # track properties that are updated
        self._store = ModelStore()
        self.load()

        # bind to track which ones need saved
        props = self.properties()
        binds = dict()
        track_change = self._track_change
        for p in props:
            ''' Because the property name is needed to track updated
            properties, using partials'''
            binds[p] = partial(track_change, p)
        self.bind(**binds)
        
        if save_on_stop:
            ''' Will watch for when the application closes and only
            binds on clock tick, so App is guaranteed to exist'''
            def bind_on_run(*args):
                App.get_running_app().bind(on_stop=self.save)
            Clock.schedule_once(bind_on_run, 0)

        if auto_save:
            ''' Use this to enforce always writing to disk, whenever a
            property changes.  '''
            props = self.properties()
            binds = dict()
            for p in props:
                binds[p] = self.save
            self.bind(**binds)

    def _track_change(self, name, *args):
        self._needs_update.add(name)
        
    def load(self):
        diff = self._store.load(self)
        if diff is not None:
            props = self.properties()
            for d in diff:
                if isinstance(props[d], ModelProperty):
                    # instantiate a class using the type,
                    # assumes type is in the global namespace
                    val = globals()[diff[d][0]](diff[d][1])
                else:
                    val = diff[d]
                props[d].set(self, val)
                
    def save(self, *args):
        ''' Properties can bass back weakrefs and other non-serializeable values
        that must be unpacked.'''
        props = self.properties()
        diff = dict()
        save_children = self._save_children
        for name in self._needs_update:
            p = props[name]
            c = p.__class__
            if c == ModelProperty:
                p = p.get(self)
                diff[name] = (p.__class__.__name__, p._key)
                if save_children:
                    p.save()
            else:
                diff[name] = p.get(self)

        self._store.update(diff)
        self._needs_update.clear()

import os
import json

class ModelStore(object):
    ''' Subclass to customize the method of saving data.  This class uses a
    simple file-backed storage using the stock json module.  Variations
    might  use a database, synchronization over network, or platform-dependent
    method.

    The persistance of this data store on Android is somewhat
    questionabale but saves seem to persist across installs and also
    whenever the user clears their cached data.  Use of a database