text: /*
  * Copyright(c) 2012-2019 Intel Corporation
  * SPDX-License-Identifier: BSD-3-Clause-Clear
  */
  
  #define _GNU_SOURCE
  #include <stdio.h>
  #include <string.h>
  #include <stdlib.h>
  #include "statistics_view.h"
  #include "statistics_view_structs.h"
  #include "statistics_view_csv.h"
  
  #define VALS_BUFFER_INIT_SIZE 10
  
  /**
   * private data of CSV output formatter
   */
  struct csv_out_prv {
  	int data_set; /* current data set number */
  	int record; /* current record number */
  	int column; /* current column number */
  	char **vals;
  	char **titles;
  	int max_vals;
  	int cur_val;
  	int max_titles;
  	int cur_title;
  };
  
  static inline int csv_is_first_record(struct view_t *this)
  {
  	return 1 == this->ctx.csv_prv->record;
  }
  
  static inline int csv_is_unit_string(const char *s)
  {
  	return NULL != s && '[' == s[0];
  }
  
  static void csv_output_column(struct view_t *this, const char *s)
  {
  	struct csv_out_prv *prv = this->ctx.csv_prv;
  
  	if (prv->column) {
  		putc(',', this->outfile);
  	}
  
  	if (strstr(s, ",")) {
  		fprintf(this->outfile, "\"%s\"", s);
  	} else {
  		fprintf(this->outfile, "%s", s);
  	}
  	prv->column++;
  }
  
  static char **csv_check_container(char **container, int *max_vals,
  					 int cur_val)
  {
  	if (!container) {
  		*max_vals = VALS_BUFFER_INIT_SIZE;
  		container = calloc(sizeof(char *), *max_vals);
  		if (!container) {
  			return NULL;
  		}
  	}
  
  	/* Resize val pointers array if needed */
  	if (*max_vals < cur_val) {
  		*max_vals = *max_vals * 2;
  		if (*max_vals < cur_val) {
  			*max_vals = cur_val;
  		}
  		container = realloc(container, sizeof(char *) * (*max_vals));
  		if (!container) {
  			return NULL;
  		}
  	}
  
  	return container;
  }
  
  static int csv_output_data(struct view_t *this, const char *s)
  {
  	struct csv_out_prv *prv = this->ctx.csv_prv;
  	if (csv_is_first_record(this)) {
  		prv->vals = csv_check_container(prv->vals, &prv->max_vals,
  						prv->cur_val+1);
  		if (!prv->vals) {
  			return 1;
  		}
  
  		/* Store value */
  		prv->vals[prv->cur_val] = strdup(s);
  		if (!prv->vals[prv->cur_val]) {
  			return 1;
  		}
  		prv->cur_val++;
  	} else {
  		csv_output_column(this, s);
  	}
  	return 0;
  }
  
  static int csv_add_column_subtitle(struct view_t *this, const char *s)
  {
  	struct csv_out_prv *prv = this->ctx.csv_prv;
  
  	prv->titles = csv_check_container(prv->titles, &prv->max_titles,
  					  prv->cur_title+1);
  	if (!prv->titles) {
  		return 1;
  	}
  
  	/* Store value */
  	prv->titles[prv->cur_title] = strdup(s);
  	if (!prv->titles[prv->cur_title]) {
  		return 1;
  	}
  	prv->cur_title++;
  
  	return 0;
  }
  
  static void csv_output_header(struct view_t *this, const char *title,
  				     const char *unit)
  {
  	static char buff[64];
  	if (unit) {
  		if (csv_is_unit_string(unit)) {
  			snprintf(buff, sizeof(buff), "%s %s", title, unit);
  		} else {
  			snprintf(buff, sizeof(buff), "%s [%s]", title, unit);
  		}
  		csv_output_column(this, buff);
  	} else {
  		csv_output_column(this, title);
  	}
  }
  
  static void csv_finish_record(struct view_t *this)
  {
  	struct csv_out_prv *prv = this->ctx.csv_prv;
  	int i;
  
  	if (prv->column) {
  		putc('\n', this->outfile);
  	}
  
  	/*
  	 * For first record we need to output stored data values
  	 */
  	if (csv_is_first_record(this)) {
  		prv->column = 0;
  		for (i = 0; i < prv->cur_val; ++i) {
  			csv_output_column(this, prv->vals[i]);
  		}
  		if (prv->column) {
  			putc('\n', this->outfile);
  		}
  	}
  	fflush(this->outfile);
  }
  
  static void csv_free_vals(struct view_t *this)
  {
  	struct csv_out_prv *prv = this->ctx.csv_prv;
  	int i;
  
  	if (prv->vals) {
  		for (i = 0; i < prv->cur_val; ++i) {
  			free(prv->vals[i]);
  		}
  		free(prv->vals);
  		prv->vals = NULL;
  		prv->cur_val = 0;
  		prv->max_vals = 0;
  	}
  }
  
  static void csv_free_titles(struct view_t *this)
  {
  	struct csv_out_prv *prv = this->ctx.csv_prv;
  	int i;
  
  	if (prv->titles) {
  		for (i = 0; i < prv->cur_title; ++i) {
  			free(prv->titles[i]);
  		}
  		free(prv->titles);
  		prv->titles = NULL;
  		prv->cur_title = 0;
  		prv->max_titles = 0;
  	}
  }
  
  int csv_process_row(struct view_t *this, int type, int num_fields, char *fields[])
  {
  	int i;
  	struct csv_out_prv *prv = this->ctx.csv_prv;
  	const char *unit = NULL;
  
  	switch (type) {
  	case DATA_SET:
  		if (prv->record) {
  			csv_finish_record(this);
  		}
  		csv_free_titles(this);
  		csv_free_vals(this);
  		if (prv->data_set) {
  			putc('\n', this->outfile);
  		}
  		if (num_fields > 0) {
  			fprintf(this->outfile, "%s\n", fields[0]);
  		}
  		prv->record = 0;
  		prv->data_set++;
  		break;
  	case RECORD:
  		if (prv->record) {
  			csv_finish_record(this);
  		}
  		prv->column = 0;
  		prv->record++;
  		break;
  
  	/*
  	 * For KV pair assume that values are interleaved
  	 * with units, so output every second value,
  	 * and use units to construct column headers.
  	 * For example:
  	 * KV_PAIR,Cache Size,10347970,[4KiB blocks],39.47,[GiB]
  	 * will result in:
  	 * data row:   10347970,39.47
  	 * header row: Cache Size [4KiB blocks],Cache Size [GiB]
  	 */
  	case KV_PAIR:
  		for (i = 1; i < num_fields; i += 2) {
  			if (csv_is_first_record(this)) {
  				if (i + 1 < num_fields) {
  					csv_output_header(this, fields[0],
  							  fields[i+1]);
  				} else {
  					csv_output_header(this, fields[0], NULL);
  				}
  			}
  			if (csv_output_data(this, fields[i])) {
  				return 1;
  			}
  		}
  		break;
  
  	/*
  	 * For table rows assume the following format:
  	 * TABLE_{ROW,SECTION},Title,value1,value2,value3,...,unit
  	 * This will result in:
  	 * data row:   value1,value2,value3,...
  	 * header row: Title [unit],Title [col1_title],Title [col2_title],...
  	 */
  	case TABLE_HEADER:
  		csv_free_titles(this);
  		csv_add_column_subtitle(this, "");
  		for (i = 2; i < num_fields; i++) {
  			if (csv_add_column_subtitle(this, fields[i])) {
  				return 1;
  			}
  		}
  		break;
  	case TABLE_SECTION:
  	case TABLE_ROW:
  		if (csv_is_first_record(this)) {
  			unit = NULL;
  			if (csv_is_unit_string(fields[num_fields-1])) {
  				unit = fields[num_fields-1];
  			}
  			csv_output_header(this, fields[0], unit);
  			for (i = 2; i < num_fields; i++) {
  				if (!csv_is_unit_string(prv->titles[i-1])) {
  					csv_output_header(this, fields[0],
  							  prv->titles[i-1]);
  				}
  			}
  		}
  		for (i = 1; i < num_fields; i++) {
  			if (!csv_is_unit_string(prv->titles[i-1])) {
  				if (csv_output_data(this, fields[i])) {
  					return 1;
  				}
  			}
  		}
  		break;
  	}
  	return 0;
  }
  
  int csv_end_input(struct view_t *this)
  {
  	csv_finish_record(this);
  	return 0;
  }
  int csv_construct(struct view_t *this)
  {
  	struct csv_out_prv *prv = calloc(sizeof(struct csv_out_prv), 1);
  
  	if (!prv) {
  		return 1;
  	}
  	this->ctx.csv_prv = prv;
  
  	return 0;
  }
  
  int csv_destruct(struct view_t *this)
  {
  	csv_free_vals(this);
  	csv_free_titles(this);
  	free(this->ctx.csv_prv);
  	return 0;
  }
  
  