<?php

// +----------------------------------------------------------------------+
// | Copyright 2013  Madpixels  (email : visualizer@madpixels.net)        |
// +----------------------------------------------------------------------+
// | This program is free software; you can redistribute it and/or modify |
// | it under the terms of the GNU General Public License, version 2, as  |
// | published by the Free Software Foundation.                           |
// |                                                                      |
// | This program is distributed in the hope that it will be useful,      |
// | but WITHOUT ANY WARRANTY; without even the implied warranty of       |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        |
// | GNU General Public License for more details.                         |
// |                                                                      |
// | You should have received a copy of the GNU General Public License    |
// | along with this program; if not, write to the Free Software          |
// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,               |
// | MA 02110-1301 USA                                                    |
// +----------------------------------------------------------------------+
// | Author: Eugene Manuilov <eugene@manuilov.org>                        |
// +----------------------------------------------------------------------+
/**
 * Source manager for local CSV files.
 *
 * @category Visualizer
 * @package Source
 *
 * @since 1.0.0
 */
class Visualizer_Source_Csv extends Visualizer_Source {

	/**
	 * The path to the file with data.
	 *
	 * @since 1.0.0
	 *
	 * @access protected
	 * @var string
	 */
	protected $_filename;

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @access public
	 * @param string $filename The path to the file.
	 */
	public function __construct( $filename = '' ) {
		$this->_filename = trim( (string) $filename );
	}

	/**
	 * Fetches series information.
	 *
	 * @since 1.0.0
	 *
	 * @access private
	 * @param resource $handle The file handle resource.
	 */
	private function _fetchSeries( &$handle ) {
		// read column titles
		$labels = fgetcsv( $handle, 0, VISUALIZER_CSV_DELIMITER, VISUALIZER_CSV_ENCLOSURE );
		$types = null;

		if ( false !== strpos( $this->_filename, 'tqx=out:csv' ) ) {
			$attributes = $this->_fetchSeriesForGoogleQueryLanguage( $labels );
			if ( ! $attributes['abort'] ) {
				$labels = $attributes['labels'];
				$types = $attributes['types'];
			}
		}

		if ( is_null( $types ) ) {
			// read series types
			$types = fgetcsv( $handle, 0, VISUALIZER_CSV_DELIMITER, VISUALIZER_CSV_ENCLOSURE );
		}

		if ( ! is_array( $labels ) || ! is_array( $types ) ) {
			$this->_error = esc_html__( 'File should have a heading row (1st row) and a data type row (2nd row). Please try again.', 'visualizer' );
			return false;
		}

		$labels = array_filter( $labels );
		$types = array_filter( $types );

		if ( ! $labels || ! $types ) {
			$this->_error = esc_html__( 'File should have a heading row (1st row) and a data type row (2nd row). Please try again.', 'visualizer' );
			return false;
		}

		$types = array_map( 'trim', $types );
		if ( ! self::_validateTypes( $types ) ) {
			$this->_error = esc_html__( 'Invalid data types detected in the data type row (2nd row). Please try again.', 'visualizer' );
			return false;
		}

		for ( $i = 0, $len = count( $labels ); $i < $len; $i++ ) {
			$default_type = $i === 0 ? 'string' : 'number';

			$labels[ $i ] = $this->toUTF8( $labels[ $i ] );

			$this->_series[] = array(
				'label' => sanitize_text_field( wp_strip_all_tags( $labels[ $i ] ) ),
				'type'  => isset( $types[ $i ] ) ? $types[ $i ] : $default_type,
			);
		}

		return true;
	}

	/**
	 * Returns file handle to fetch data from.
	 *
	 * @since 1.4.2
	 *
	 * @access protected
	 * @param string $filename Optional file name to get handle. If omitted, $_filename is used.
	 * @return resource|false File handle resource on success, otherwise FALSE.
	 */
	protected function _get_file_handle( $filename = false ) {
		// open file and return handle
		return fopen( $filename ? $filename : $this->_filename, 'rb' );
	}

	/**
	 * Fetches information from source, parses it and builds series and data arrays.
	 *
	 * @since 1.0.0
	 *
	 * @access public
	 * @return boolean TRUE on success, otherwise FALSE.
	 */
	public function fetch() {
		// if filename is empty return false
		if ( empty( $this->_filename ) ) {
			$this->_error = esc_html__( 'No file provided. Please try again.', 'visualizer' );
			return false;
		}

		// read file and fill arrays
		$handle = $this->_get_file_handle();
		if ( ! $handle ) {
			if ( empty( $this->_error ) ) {
				$this->_error = esc_html__( 'The file could not be opened. Please try again.', 'visualizer' );
			}
			return false;
		}

		// fetch series
		if ( ! $this->_fetchSeries( $handle ) ) {
			fclose( $handle );
			return false;
		}

		// fetch data
		$data = fgetcsv( $handle, 0, VISUALIZER_CSV_DELIMITER, VISUALIZER_CSV_ENCLOSURE );
		while ( $data !== false ) {
			$this->_data[] = $this->_normalizeData( $data );
			$data = fgetcsv( $handle, 0, VISUALIZER_CSV_DELIMITER, VISUALIZER_CSV_ENCLOSURE );
		}

		// close file handle
		fclose( $handle );

		return true;
	}

	/**
	 * Returns source name.
	 *
	 * @since 1.0.0
	 *
	 * @access public
	 * @return string The name of source.
	 */
	public function getSourceName() {
		return __CLASS__;
	}

	/**
	 * Adds support for QueryLanguage https://developers.google.com/chart/interactive/docs/querylanguage
	 * where the user can provide something like /gviz/tq?tq=select%20A%2C%20B%20&tqx=out:csv after the URL of the raw spreadsheet
	 * to get the subset of data specified by the query
	 * this will conflate the heading and the type into one value viz. for heading XXX and type string, the value will become "XXX string"
	 * so we need to split them apart logically
	 * also the $types variable now contains the first row because the header and the type got conflated
	 */
	private function _fetchSeriesForGoogleQueryLanguage( $labels, $types = array() ) {
		$new_labels = array();
		$new_types = array();
		$abort = false;
		foreach ( $labels as $label ) {
			// get the index of the last space
			$index = strrpos( $label, ' ' );
			if ( $index === false ) {
				// no space here? something has gone wrong; abort the entire process.
				$abort = true;
				break;
			}
			$type = trim( substr( $label, $index + 1 ) );
			if ( ! self::_validateTypes( array( $type ) ) ) {
				// some other data type? abort the entire process.
				$abort = true;
				break;
			}
			$label = substr( $label, 0, $index );
			$new_labels[] = $label;
			$new_types[] = $type;
		}
		if ( ! $abort ) {
			$labels = $new_labels;
			$types = $new_types;
		}

		return array(
			'abort' => $abort,
			'labels'    => $labels,
			'types' => $types,
		);
	}
}
