from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals


import os
import csv
import json
import requests
import six

from .exceptions import (
    ResourceError
)


class Resource(object):
    @classmethod
    def load(cls, metadata, default_base_path=None):
        try:
            resource = TabularResource(metadata, default_base_path)
        except ValueError:
            resource = cls(metadata, default_base_path)
        return resource

    def __init__(self, metadata, default_base_path=None):
        self._metadata = metadata
        self._base_path = self.metadata.get('base', default_base_path)
        self._data = self._parse_data(metadata)

    @property
    def metadata(self):
        return self._metadata

    @property
    def data(self):
        return self._data

    def _parse_data(self, metadata):
        return self._load_data(metadata)

    def _load_data(self, metadata):
        inline_data = metadata.get('data')
        data_path = metadata.get('path')
        data_url = metadata.get('url')
        data = None

        if inline_data is not None:
            data = inline_data
        elif data_path is not None:
            path = self._absolute_path(data_path)
            try:
                with open(path, 'r') as f:
                    data = f.read()
                    if six.PY2:
                        data = unicode(data, 'utf-8')
            except IOError as e:
                six.raise_from(ResourceError(e), e)
        elif data_url is not None:
            try:
                req = requests.get(data_url)
                req.raise_for_status()
                data = req.text
            except requests.exceptions.RequestException as e:
                six.raise_from(ResourceError(e), e)

        return data

    def _absolute_path(self, path):
        if self._base_path is None:
            return path
        return os.path.join(self._base_path, path)


class TabularResource(Resource):
    def _parse_data(self, metadata):
        data = self._load_data(metadata)

        if isinstance(data, six.string_types):
            try:
                data = json.loads(data)
            except ValueError:
                data = [row for row in _csv_dictreader(six.StringIO(data))]
                if not data:
                    data = None

        if not self._is_tabular_data(data):
            raise ValueError()

        return data

    def _is_tabular_data(self, data):
        return (isinstance(data, dict) or
                isinstance(data, list) or
                isinstance(data, tuple))


if six.PY2:
    def _csv_dictreader(data, dialect=csv.excel, **kwargs):
        """Read text stream (unicode on Py2.7) as CSV."""

        def iterenc_utf8(data):
            for line in data:
                yield line.encode('utf-8')

        reader = csv.DictReader(iterenc_utf8(data), dialect=dialect, **kwargs)
        for row in reader:
            yield dict([(unicode(k, 'utf-8'), unicode(v, 'utf-8'))
                        for (k, v) in row.items()])
else:
    _csv_dictreader = csv.DictReader

@@ -285,73 +285,1141 @@
     
-@classmethod%0A    def load(cls, metadata, default_base_path=None):
+'''Base class for all Data Package's resource types.%0A%0A    This classes will usually be created by :class:%60DataPackage%60, and not by%0A    you. If you need to create one, use the :func:%60Resource.load%60 factory%0A    method.%0A    '''%0A%0A    @classmethod%0A    def load(cls, metadata, default_base_path=None):%0A        '''Factory method that loads the resource described in %60%60metadata%60%60.%0A%0A        It'll first try to load the resource defined in %60%60metadata%60%60 as a%0A        :class:%60TabularResource%60. If that fails, it'll fall back to loading it%0A        as a :class:%60Resource%60.%0A%0A        Args:%0A            metadata (dict): The dict with the resource's metadata%0A            default_base_path (str, optional): The base path to be used in case%0A                the resource's data is in the local disk. Usually this would be%0A                the base path of the %60datapackage.json%60 this resource is in.%0A%0A        Returns:%0A            Resource: The returned resource's class will depend on the type of%0A                resource. If it was tabular, a :class:%60TabularResource%60 will be%0A                returned, otherwise, it'll be a :class:%60Resource%60.%0A        '''
 %0A   
@@ -1846,32 +1846,97 @@
 metadata(self):%0A
+        '''dict: The metadata this resource was created with.'''%0A
         return s
@@ -1976,32 +1976,73 @@
 def data(self):%0A
+        '''str: This resource's data.'''%0A
         return s
@@ -3225,40 +3225,485 @@
     
-def _parse_data(self, metadata):
+'''Subclass of :class:%60Resource%60 that deals with tabular data.%0A%0A    It currently only supports CSVs.%0A    '''%0A%0A    @property%0A    def data(self):%0A        '''list: This resource's data.'''%0A        return super(TabularResource, self).data%0A%0A    def _parse_data(self, metadata):%0A        '''Parses the data%0A%0A        Raises:%0A            ValueError: If the data isn't tabular. We consider tabular data as%0A                a %60%60dict%60%60, %60%60list%60%60, %60%60tuple%60%60, %60%60JSON%60%60 or %60%60CSV%60%60.%0A        '''
 %0A   
@@ -4356,19 +4356,19 @@
         
-%22%22%22
+'''
 Read tex
@@ -4406,11 +4406,11 @@
 CSV.
-%22%22%22
+'''
 %0A%0A  

Document `datapackage.resource`