from inspect import signature, Parameter
from functools import wraps
from typing import Optional
import re


CAMEL_REGEX = re.compile(r'([A-Z])')
UNDERSCORE_REGEX = re.compile(r'_([a-z])')

def camel_to_underscore(name):
    #TODO: need to rename 'from'/'to' to 'from_timestamp'/'to_timestamp' everywhere(in analytics, server, panel)
    if name == 'from' or name == 'to':
        name += '_timestamp'
    return CAMEL_REGEX.sub(lambda x: '_' + x.group(1).lower(), name)

def underscore_to_camel(name):
    if name == 'from_timestamp' or name == 'to_timestamp':
        name = name.replace('_timestamp', '')
    return UNDERSCORE_REGEX.sub(lambda x: x.group(1).upper(), name)

def is_field_private(field_name: str) -> Optional[str]:
    m = re.match(r'_[^(__)]+__', field_name)
    return m is not None

def serialize(obj):
    if hasattr(obj, 'to_json') == True:
        return obj.to_json()
    else:
        return obj

def inited_params(target_init):
    target_params = signature(target_init).parameters.values()
    if len(target_params) < 1:
        raise ValueError('init function mush have at least self parameter')
    if len(target_params) == 1:
        return target_init
    _, *target_params = target_params # we will not use self any more

    @wraps(target_init)
    def wrapped_init(wrapped_self, *wrapped_args, **wrapped_kwargs):
        for tp in target_params:
            if tp.default is Parameter.empty:
                continue
            setattr(wrapped_self, tp.name, tp.default)

        for tp, v in zip(target_params, wrapped_args):
            setattr(wrapped_self, tp.name, v)

        for k, v in wrapped_kwargs.items():
            setattr(wrapped_self, k, v)

        target_init(wrapped_self, *wrapped_args, **wrapped_kwargs)

    return wrapped_init

def JSONClass(target_class):

    def to_json(self) -> dict:
        """
        returns a json representation of the class
        where all None - values and private fileds are skipped
        """
        return {
            underscore_to_camel(k): serialize(v) for k, v in self.__dict__.items()
            if v is not None and not is_field_private(k)
        }

    def from_json(json_object: Optional[dict]) -> target_class:
        if json_object is None:
            json_object = {}
        init_object = { camel_to_underscore(k): v for k, v in json_object.items() }
        return target_class(**init_object)

    # target_class.__init__ = inited_params(target_class.__init__)
    target_class.to_json = to_json
    target_class.from_json = from_json
    return target_class

class SerializableList(list):
    def to_json(self):
        return list(map(lambda s: s.to_json(), self))

from inspect import signature, Parameter
from functools import wraps
from typing import Optional
import re


CAMEL_REGEX = re.compile(r'([A-Z])')
UNDERSCORE_REGEX = re.compile(r'_([a-z])')

def camel_to_underscore(name):
    #TODO: need to rename 'from'/'to' to 'from_timestamp'/'to_timestamp' everywhere(in analytics, server, panel)
    if name == 'from' or name == 'to':
        name += '_timestamp'
    return CAMEL_REGEX.sub(lambda x: '_' + x.group(1).lower(), name)

def underscore_to_camel(name):
    if name == 'from_timestamp' or name == 'to_timestamp':
        name = name.replace('_timestamp', '')
    return UNDERSCORE_REGEX.sub(lambda x: x.group(1).upper(), name)

def is_field_private(field_name: str) -> Optional[str]:
    m = re.match(r'_[^(__)]+__', field_name)
    return m is not None

def serialize(obj):
    if hasattr(obj, 'to_json') == True:
        return obj.to_json()
    else:
        return obj

def inited_params(target_init):
    target_params = signature(target_init).parameters.values()
    if len(target_params) < 1:
        raise ValueError('init function mush have at least self parameter')
    if len(target_params) == 1:
        return target_init
    _, *target_params = target_params # we will not use self any more

    @wraps(target_init)
    def wrapped_init(wrapped_self, *wrapped_args, **wrapped_kwargs):
        for tp in target_params:
            if tp.default is Parameter.empty:
                continue
            setattr(wrapped_self, tp.name, tp.default)

        for tp, v in zip(target_params, wrapped_args):
            setattr(wrapped_self, tp.name, v)

        for k, v in wrapped_kwargs.items():
            setattr(wrapped_self, k, v)

        target_init(wrapped_self, *wrapped_args, **wrapped_kwargs)

    return wrapped_init

def JSONClass(target_class):

    def to_json(self) -> dict:
        """
        returns a json representation of the class
        where all None - values and private fileds are skipped
        """
        return {
            underscore_to_camel(k): serialize(v) for k, v in self.__dict__.items()
            if v is not None and not is_field_private(k)
        }

    def from_json(json_object: Optional[dict]) -> target_class:
        if json_object is None:
            json_object = {}
        init_object = { camel_to_underscore(k): v for k, v in json_object.items() }
        return target_class(**init_object)

    # target_class.__init__ = inited_params(target_class.__init__)
    target_class.to_json = to_json
    target_class.from_json = from_json
    return target_class

class SerializableList(list):
    def to_json(self):
        return list(map(lambda s: s.to_json(), self))

#TODO: need to rename 'from'/'to' to 'from_timestamp'/'to_timestamp' everywhere(in analytics, server, panel) # we will not use self any more returns a json representation of the class         where all None - values and private fileds are skipped # target_class.__init__ = inited_params(target_class.__init__)