# -*- coding: utf-8 -*-
"""
CSV
===
Interface to read and write csv files.
Note
----
The module utilizes the csv file writer/reader from numpy.
"""
__author__    = 'Christoph Kirst <christoph.kirst.ck@gmail.com>'
__license__   = 'GPLv3 - GNU General Pulic License v3 (see LICENSE.txt)'
__copyright__ = 'Copyright © 2020 by Christoph Kirst'
__webpage__   = 'http://idisco.info'
__download__  = 'http://www.github.com/ChristophKirst/ClearMap2'
import numpy as np
import ClearMap.IO.Source as src
import ClearMap.IO.Slice as slc
###############################################################################
### Source classe
###############################################################################
[docs]
class Source(src.Source):
  """CSV array source."""
  
  def __init__(self, location):
    """CSV source class construtor.
    
    Arguments
    ---------
    location : str
      The filename of the csv source.
    """
    self._location = location;
   
  @property
  def name(self):
    return "Csv-Source";  
  
  @property
  def location(self):
    return self._location;
    
  @location.setter
  def location(self, value):
    if value != self.location:
      self._location = value;
  @property
  def array(self):
    """The underlying data array.
    
    Returns
    -------
    array : array
      The underlying data array of this source.
    """
    return _array(self.location);
  
  @array.setter
  def array(self, value):
    _write(self.location, value);
  
  @property 
  def shape(self):
    """The shape of the source.
    
    Returns
    -------
    shape : tuple
      The shape of the source.
    """
    return self.array.shape;
  
  @shape.setter
  def shape(self, value):
    raise NotImplementedError('Cannot set shape of csv file');
    
  
  @property 
  def dtype(self):
    """The data type of the source.
    
    Returns
    -------
    dtype : dtype
      The data type of the source.
    """
    return self.array.dtype;
  
  @dtype.setter
  def dtype(self, value):
    raise NotImplementedError('Cannot set dtype of csv file');
  
    
  @property 
  def order(self):
    """The order of how the data is stored in the source.
    
    Returns
    -------
    order : str
      Returns 'C' for C contigous and 'F' for fortran contigous, None otherwise.
    """
    return self.array.order;
  
  @order.setter
  def order(self, value):
    raise NotImplementedError('Cannot set order of csv file');
    
  
  @property
  def element_strides(self):
    """The strides of the array elements.
    
    Returns
    -------
    strides : tuple
      Strides of the array elements.
      
    Note
    ----
    The strides of the elements module itemsize instead of bytes.
    """
    array = self.array;
    return tuple(s // array.itemsize for s in array.strides)
  
  
  @property
  def offset(self):
    """The offset of the memory map in the file.
    
    Returns
    -------
    offset : int
      Offset of the memeory map in the file.
    """
    return 0;
  
  
  ### Data
  def __getitem__(self, *args):
    array = _array(self.location);
    return array.__getitem__(*args);
  def __setitem__(self, *args):
    array = _array(self.location);
    array.__setitem__(*args);
    _write(self.location, array);
  
  
[docs]
  def as_memmap(self):
     raise NotImplementedError('Memmap creation not implemented yet!') 
  
  
[docs]
  def as_virtual(self):
     return VirtualSource(source=self); 
     
[docs]
  def as_real(self):
    return self; 
  
[docs]
  def as_buffer(self):
    return self.array; 
  
  
  ### Formatting
  def __str__(self):
    try:
      name = self.name;
      name = '%s' % name if name is not None else '';
    except:
      name ='';
    
    try:
      array = self.array;
    except:
      array = None;
      
    try:
      shape = array.shape
      shape ='%r' % ((shape,)) if shape is not None else '';
    except:
      shape = '';
    try:
      dtype = array.dtype;
      dtype = '[%s]' % dtype if dtype is not None else '';
    except:
      dtype = '';
            
    try:
      order = array.order;
      order = '|%s|' % order if order is not None else '';
    except:
      order = '';
    
    try:
      location = self.location;
      location = '%s' % location if location is not None else '';
      if len(location) > 100:
        location = location[:50] + '...' + location[-50:]
      if len(location) > 0:
        location = '{%s}' % location;
    except:
      location = '';    
    
    return name + shape + dtype + order + location 
[docs]
class VirtualSource(src.VirtualSource):
  def __init__(self, source = None, shape = None, dtype = None, order = None, location = None, name = None):
    super(VirtualSource, self).__init__(source=source, shape=shape, dtype=dtype, order=order, location=location, name=name);
    if isinstance(source, Source):
      self.location = source.location;
  
  @property 
  def name(self):
    return 'Virtual-Csv-Source';
  
[docs]
  def as_virtual(self):
    return self; 
  
[docs]
  def as_real(self):
    return Source(location=self.location); 
  
[docs]
  def as_buffer(self):
    return self.as_real().as_buffer(); 
 
###############################################################################
### IO Interface
###############################################################################
[docs]
def is_csv(source):
  """Checks if this source is a CSV source"""
  if isinstance(source, Source):
    return True;
  if isinstance(source, str) and len(source) >= 3 and source[-3:] == 'csv':
    return True;
  return False; 
[docs]
def read(source, slicing = None, as_source = None, **kwargs):
  """Read data from a csv file.
  
  Arguments
  ---------
  source : str
    The name of the CSV file.
  slicing : slice, Slice or None
    An optional sub-slice to consider.
  as_source : bool
    If True, return results as a source.
  
  Returns
  -------
  array : array
    The data in the csv file as a buffer or source.
  """ 
  if not isinstance(source, Source):
    source = Source(source);
  if slicing is None:
    if as_source:
      return source;
    else:
      return source.array
  else:
    if as_source:
      return slc.Slice(source, slicing=slicing);
    else:
      return source.__getitem__(slicing); 
  
[docs]
def write(sink, data, slicing = None, **kwargs):
  """Write data to a csv file.
  
  Arguments
  ---------
  sink : str
    The name of the CSV file.
  data : array 
    The data to write into the CSV file.
  slicing : slice, Slice or None
    An optional sub-slice to consider.
  
  Returns
  -------
  sink : array or source
    The sink csv file.
  """ 
  if not isinstance(sink, Source):
    sink = Source(sink);
  if slicing is not None:
    array = sink.array;
    array[slicing]= data; 
  else:
    array = data;
  
  return _write(sink, array); 
[docs]
def create(location = None, shape = None, dtype = None, order = None, mode = None, array = None, as_source = True, **kwargs):
  raise NotImplementedError('Creating CSV files not implemented yet!')  
###############################################################################
### Helpers
###############################################################################
def _write(filename, points, **args):
    """Write point data to csv file
    """
    np.savetxt(filename, points, delimiter=',', newline='\n', fmt='%.5e')
    return filename
def _array(location, delimeter = ',', **args):
    """Read data from csv file.
    
    Arguments
    ---------
    location : str
      Location of the csv array data.
    delimteter : char
      The delimater between subsequent array entries.
    
    Returns
    -------
    array : array
      The data as a numpy array.
    """
    points = np.loadtxt(location, delimiter=delimeter);
    return points;
###############################################################################
### Tests
###############################################################################
[docs]
def test():    
    """Test CSV module"""
    import os
    import numpy as np
    import ClearMap.IO.CSV as csv
    
    location = 'test.csv';
    points = np.random.rand(5,3);
 
    s = csv.Source(location);
    print(s)    
    s.array = points;
    print(s)    
    
    os.remove(location)