# pylint: disable-msg=E1101,W0613,W0603
from itertools import islice
import os
import numpy as np

import pandas._libs.json as json
from pandas._libs.tslibs import iNaT
from pandas.compat import StringIO, long, u, to_str
from pandas import compat, isna
from pandas import Series, DataFrame, to_datetime, MultiIndex
from pandas.io.common import (get_filepath_or_buffer, _get_handle,
                              _infer_compression, _stringify_path,
                              BaseIterator)
from pandas.io.parsers import _validate_integer
import pandas.core.common as com
from pandas.core.reshape.concat import concat
from pandas.io.formats.printing import pprint_thing
from .normalize import _convert_to_line_delimits
from .table_schema import build_table_schema, parse_table_schema
from pandas.core.dtypes.common import is_period_dtype

loads = json.loads
dumps = json.dumps

TABLE_SCHEMA_VERSION = '0.20.0'


# interface to/from
def to_json(path_or_buf, obj, orient=None, date_format='epoch',
            double_precision=10, force_ascii=True, date_unit='ms',
            default_handler=None, lines=False, compression='infer',
            index=True):

    if not index and orient not in ['split', 'table']:
        raise ValueError("'index=False' is only valid when 'orient' is "
                         "'split' or 'table'")

    path_or_buf = _stringify_path(path_or_buf)
    if lines and orient != 'records':
        raise ValueError(
            "'lines' keyword only valid when 'orient' is records")

    if orient == 'table' and isinstance(obj, Series):
        obj = obj.to_frame(name=obj.name or 'values')
    if orient == 'table' and isinstance(obj, DataFrame):
        writer = JSONTableWriter
    elif isinstance(obj, Series):
        writer = SeriesWriter
    elif isinstance(obj, DataFrame):
        writer = FrameWriter
    else:
        raise NotImplementedError("'obj' should be a Series or a DataFrame")

    s = writer(
        obj, orient=orient, date_format=date_format,
        double_precision=double_precision, ensure_ascii=force_ascii,
        date_unit=date_unit, default_handler=default_handler,
        index=index).write()

    if lines:
        s = _convert_to_line_delimits(s)

    if isinstance(path_or_buf, compat.string_types):
        fh, handles = _get_handle(path_or_buf, 'w', compression=compression)
        try:
            fh.write(s)
        finally:
            fh.close()
    elif path_or_buf is None:
        return s
    else:
        path_or_buf.write(s)


class Writer(object):

    def __init__(self, obj, orient, date_format, double_precision,
                 ensure_ascii, date_unit, index, default_handler=None):
        self.obj = obj

        if orient is None:
            orient = self._default_orient

        self.orient = orient
        self.date_format = date_format
        self.double_precision = double_precision
        self.ensure_ascii = ensure_ascii
        self.date_unit = date_unit
        self.default_handler = default_handler
        self.index = index

        self.is_copy = None
        self._format_axes()

    def _format_axes(self):
        raise com.AbstractMethodError(self)

    def write(self):
        return self._write(self.obj, self.orient, self.double_precision,
                           self.ensure_ascii, self.date_unit,
                           self.date_format == 'iso', self.default_handler)

    def _write(self, obj, orient, double_precision, ensure_ascii,
               date_unit, iso_dates, default_handler):
        return dumps(
            obj,
            orient=orient,
            double_precision=double_precision,
            ensure_ascii=ensure_ascii,
            date_unit=date_unit,
            iso_dates=iso_dates,
            default_handler=default_handler
        )


class SeriesWriter(Writer):
    _default_orient = 'index'

    def _format_axes(self):
        if not self.obj.index.is_unique and self.orient == 'index':
            raise ValueError("Series index must be unique for orient="
                             "'{orient}'".format(orient=self.orient))

    def _write(self, obj, orient, double_precision, ensure_ascii,
               date_unit, iso_dates, default_handler):
        if not self.index and orient == 'split':
            obj = {"name": obj.name, "data": obj.values}
        return super(SeriesWriter, self)._write(obj, orient,
                                                double_precision,
                                                ensure_ascii, date_unit,
                                                iso_dates, default_handler)


class FrameWriter(Writer):
    _default_orient = 'columns'

    def _format_axes(self):
        """ try to axes if they are datelike """
        if not self.obj.index.is_unique and self.orient in (
                'index', 'columns'):
            raise ValueError("DataFrame index must be unique for orient="
                             "'{orient}'.".format(orient=self.orient))
        if not self.obj.columns.is_unique and self.orient in (
                'index', 'columns', 'records'):
            raise ValueError("DataFrame columns must be unique for orient="
                             "'{orient}'.".format(orient=self.orient))

    def _write(self, obj, orient, double_precision, ensure_ascii,
               date_unit, iso_dates, default_handler):
        if not self.index and orient == 'split':
            obj = obj.to_dict(orient='split')
            del obj["index"]
        return super(FrameWriter, self)._write(obj, orient,
                                               double_precision,
                                               ensure_ascii, date_unit,
                                               iso_dates, default_handler)


class JSONTableWriter(FrameWriter):
    _default_orient = 'records'

    def __init__(self, obj, orient, date_format, double_precision,
                 ensure_ascii, date_unit, index, default_handler=None):
        """
        Adds a `schema` attribute with the Table Schema, resets
        the index (can't do in caller, because the schema inference needs
        to know what the index is, forces orient to records, and forces
        date_format to 'iso'.
        """
        super(JSONTableWriter, self).__init__(
            obj, orient, date_format, double_precision, ensure_ascii,
            date_unit, index, default_handler=default_handler)

        if date_format != 'iso':
            msg = ("Trying to write with `orient='table'` and "
                   "`date_format='{fmt}'`. Table Schema requires dates "
                   "to be formatted with `date_format='iso'`"
                   .format(fmt=date_format))
            raise ValueError(msg)

        self.schema = build_table_schema(obj, index=self.index)

        # NotImplementd on a column MultiIndex
        if obj.ndim == 2 and isinstance(obj.columns, MultiIndex):