from __future__ import absolute_import, division, print_function

from datetime import datetime
from functools import partial

from datashape import discover
from datashape import float32, float64, string, Option, object_, datetime_
import datashape

import pandas as pd
import numpy as np

from ..convert import convert


possibly_missing = set((string, datetime_, float32, float64))


@discover.register(pd.DataFrame)
def discover_dataframe(df):
    obj = object_
    names = list(df.columns)
    dtypes = list(map(datashape.CType.from_numpy_dtype, df.dtypes))
    dtypes = [string if dt == obj else dt for dt in dtypes]
    odtypes = [Option(dt) if dt in possibly_missing else dt
               for dt in dtypes]
    schema = datashape.Record(list(zip(names, odtypes)))
    return len(df) * schema


@discover.register(pd.Series)
def discover_series(s):
    return len(s) * datashape.CType.from_numpy_dtype(s.dtype)


def coerce_datetimes(df):
    """ Make object columns into datetimes if possible

    Warning: this operates inplace.

    Example
    -------

    >>> df = pd.DataFrame({'dt': ['2014-01-01'], 'name': ['Alice']})
    >>> df.dtypes  # note that these are strings/object
    dt      object
    name    object
    dtype: object

    >>> df2 = coerce_datetimes(df)
    >>> df2
              dt   name
    0 2014-01-01  Alice

    >>> df2.dtypes  # note that only the datetime-looking-one was transformed
    dt      datetime64[ns]
    name            object
    dtype: object
    """

    def patched_to_datetime(s):
        # NOTE: In pandas < 0.17, pd.to_datetime(' ') == datetime(...), which
        # is not what we want.  This patched version detects empty or
        # whitespace-only strings, and maps them to a dummy object.  This
        # prevents datetime coercion on whitespace or empty strings.
        o = object()
        s_norm = s.apply(lambda x: o if (hasattr(x, 'strip') and not x.strip()) else x)
        res = pd.to_datetime(s_norm, errors='ignore')
        return res.apply(lambda x: '' if x is o else x)

    df2 = df.select_dtypes(include=['object']).apply(patched_to_datetime)
    for c in df2.columns:
        df[c] = df2[c]
    return df


@convert.register(pd.Timestamp, datetime)
def convert_datetime_to_timestamp(dt, **kwargs):
    return pd.Timestamp(dt)


@convert.register(pd.Timestamp, float)
def nan_to_nat(fl, **kwargs):
    try:
        if np.isnan(fl):
            # Only nan->nat edge
            return pd.NaT
    except TypeError:
        pass
    raise NotImplementedError()


@convert.register(pd.Timestamp, (pd.tslib.NaTType, type(None)))
def convert_null_or_nat_to_nat(n, **kwargs):
    return pd.NaT

@@ -1504,39 +1504,48 @@
    d
-ef patched_to_datetime(s):%0A    
+f2 = df.select_dtypes(include=%5Bobject%5D)%0A
     
@@ -1617,21 +1617,17 @@
 hich
-%0A        # is
+ is%0A    #
  not
@@ -1646,36 +1646,48 @@
 t.  
-This patched version detects
+So we first remove all columns that have
  emp
@@ -1700,20 +1700,16 @@
     
-    
 # whit
-e
 s
+e
 pace
@@ -1725,58 +1725,29 @@
 ings
-, and maps them to a dummy object.  This%0A        #
+ to compensate.  This
  pre
@@ -1769,16 +1769,22 @@
 coercion
+%0A    #
  on whit
@@ -1816,43 +1816,30 @@
     
-    o = object()%0A        s_norm = s
+empty_or_ws_cols = df2
 .app
@@ -1852,247 +1852,203 @@
 bda 
-x: o if (hasattr(x, 'strip') and not x.strip()) else x
+s: s.str.strip() == '').all(
 )%0A    
-    res = pd.to_datetime(s_norm, errors='ignore')%0A        return res.apply(lambda x: '' if x is o else x)%0A
+for colname, remove in empty_or_ws_cols.iteritems():%0A        if remove:%0A            del df2%5Bcolname%5D
 %0A    df
-2
+3
  = df
-.select_dtypes(include=%5B'object'%5D).apply(patched_to_datetime
+2.apply(partial(pd.to_datetime, errors='ignore')
 )%0A  
@@ -2060,17 +2060,17 @@
  c in df
-2
+3
 .columns
@@ -2089,17 +2089,17 @@
 %5Bc%5D = df
-2
+3
 %5Bc%5D%0A    

Improve CSV datetime coercion performance.