/*
 * libxlsxwriter
 *
 * Copyright 2014-2020, John McNamara, jmcnamara@cpan.org. See LICENSE.txt.
 */

/**
 * @page workbook_page The Workbook object
 *
 * The Workbook is the main object exposed by the libxlsxwriter library. It
 * represents the entire spreadsheet as you see it in Excel and internally it
 * represents the Excel file as it is written on disk.
 *
 * See @ref workbook.h for full details of the functionality.
 *
 * @file workbook.h
 *
 * @brief Functions related to creating an Excel xlsx workbook.
 *
 * The Workbook is the main object exposed by the libxlsxwriter library. It
 * represents the entire spreadsheet as you see it in Excel and internally it
 * represents the Excel file as it is written on disk.
 *
 * @code
 *     #include "xlsxwriter.h"
 *
 *     int main() {
 *
 *         lxw_workbook  *workbook  = workbook_new("filename.xlsx");
 *         lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL);
 *
 *         worksheet_write_string(worksheet, 0, 0, "Hello Excel", NULL);
 *
 *         return workbook_close(workbook);
 *     }
 * @endcode
 *
 * @image html workbook01.png
 *
 */
#ifndef __LXW_WORKBOOK_H__
#define __LXW_WORKBOOK_H__

#include <stdint.h>
#include <stdio.h>
#include <errno.h>

#include "worksheet.h"
#include "chartsheet.h"
#include "chart.h"
#include "shared_strings.h"
#include "hash_table.h"
#include "common.h"

#define LXW_DEFINED_NAME_LENGTH 128

/* Define the tree.h RB structs for the red-black head types. */
RB_HEAD(lxw_worksheet_names, lxw_worksheet_name);
RB_HEAD(lxw_chartsheet_names, lxw_chartsheet_name);
RB_HEAD(lxw_image_md5s, lxw_image_md5);

/* Define the queue.h structs for the workbook lists. */
STAILQ_HEAD(lxw_sheets, lxw_sheet);
STAILQ_HEAD(lxw_worksheets, lxw_worksheet);
STAILQ_HEAD(lxw_chartsheets, lxw_chartsheet);
STAILQ_HEAD(lxw_charts, lxw_chart);
TAILQ_HEAD(lxw_defined_names, lxw_defined_name);

/* Struct to hold the 2 sheet types. */
typedef struct lxw_sheet {
    uint8_t is_chartsheet;

    union {
        lxw_worksheet *worksheet;
        lxw_chartsheet *chartsheet;
    } u;

    STAILQ_ENTRY (lxw_sheet) list_pointers;
} lxw_sheet;

/* Struct to represent a worksheet name/pointer pair. */
typedef struct lxw_worksheet_name {
    const char *name;
    lxw_worksheet *worksheet;

    RB_ENTRY (lxw_worksheet_name) tree_pointers;
} lxw_worksheet_name;

/* Struct to represent a chartsheet name/pointer pair. */
typedef struct lxw_chartsheet_name {
    const char *name;
    lxw_chartsheet *chartsheet;

    RB_ENTRY (lxw_chartsheet_name) tree_pointers;
} lxw_chartsheet_name;

/* Struct to represent an image MD5/ID pair. */
typedef struct lxw_image_md5 {
    uint32_t id;
    char *md5;

    RB_ENTRY (lxw_image_md5) tree_pointers;
} lxw_image_md5;

/* Wrapper around RB_GENERATE_STATIC from tree.h to avoid unused function
 * warnings and to avoid portability issues with the _unused attribute. */
#define LXW_RB_GENERATE_WORKSHEET_NAMES(name, type, field, cmp)  \
    RB_GENERATE_INSERT_COLOR(name, type, field, static)          \
    RB_GENERATE_REMOVE_COLOR(name, type, field, static)          \
    RB_GENERATE_INSERT(name, type, field, cmp, static)           \
    RB_GENERATE_REMOVE(name, type, field, static)                \
    RB_GENERATE_FIND(name, type, field, cmp, static)             \
    RB_GENERATE_NEXT(name, type, field, static)                  \
    RB_GENERATE_MINMAX(name, type, field, static)                \
    /* Add unused struct to allow adding a semicolon */          \
    struct lxw_rb_generate_worksheet_names{int unused;}

#define LXW_RB_GENERATE_CHARTSHEET_NAMES(name, type, field, cmp) \
    RB_GENERATE_INSERT_COLOR(name, type, field, static)          \
    RB_GENERATE_REMOVE_COLOR(name, type, field, static)          \
    RB_GENERATE_INSERT(name, type, field, cmp, static)           \
    RB_GENERATE_REMOVE(name, type, field, static)                \
    RB_GENERATE_FIND(name, type, field, cmp, static)             \
    RB_GENERATE_NEXT(name, type, field, static)                  \
    RB_GENERATE_MINMAX(name, type, field, static)                \
    /* Add unused struct to allow adding a semicolon */          \
    struct lxw_rb_generate_charsheet_names{int unused;}

#define LXW_RB_GENERATE_IMAGE_MD5S(name, type, field, cmp) \
    RB_GENERATE_INSERT_COLOR(name, type, field, static)          \
    RB_GENERATE_REMOVE_COLOR(name, type, field, static)          \
    RB_GENERATE_INSERT(name, type, field, cmp, static)           \
    RB_GENERATE_REMOVE(name, type, field, static)                \
    RB_GENERATE_FIND(name, type, field, cmp, static)             \
    RB_GENERATE_NEXT(name, type, field, static)                  \
    RB_GENERATE_MINMAX(name, type, field, static)                \
    /* Add unused struct to allow adding a semicolon */          \
    struct lxw_rb_generate_image_md5s{int unused;}

/**
 * @brief Macro to loop over all the worksheets in a workbook.
 *
 * This macro allows you to loop over all the worksheets that have been
 * added to a workbook. You must provide a lxw_worksheet pointer and
 * a pointer to the lxw_workbook:
 *
 * @code
 *    lxw_workbook  *workbook = workbook_new("test.xlsx");
 *
 *    lxw_worksheet *worksheet; // Generic worksheet pointer.
 *
 *    // Worksheet objects used in the program.
 *    lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL);
 *    lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL);
 *    lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, NULL);
 *
 *    // Iterate over the 3 worksheets and perform the same operation on each.
 *    LXW_FOREACH_WORKSHEET(worksheet, workbook) {
 *        worksheet_write_string(worksheet, 0, 0, "Hello", NULL);
 *    }
 * @endcode
 */
#define LXW_FOREACH_WORKSHEET(worksheet, workbook) \
    STAILQ_FOREACH((worksheet), (workbook)->worksheets, list_pointers)

/* Struct to represent a defined name. */
typedef struct lxw_defined_name {
    int16_t index;
    uint8_t hidden;
    char name[LXW_DEFINED_NAME_LENGTH];
    char app_name[LXW_DEFINED_NAME_LENGTH];
    char formula[LXW_DEFINED_NAME_LENGTH];
    char normalised_name[LXW_DEFINED_NAME_LENGTH];
    char normalised_sheetname[LXW_DEFINED_NAME_LENGTH];

    /* List pointers for queue.h. */
    TAILQ_ENTRY (lxw_defined_name) list_pointers;
} lxw_defined_name;

/**
 * Workbook document properties. Set any unused fields to NULL or 0.
 */
typedef struct lxw_doc_properties {
    /** The title of the Excel Document. */
    char *title;

    /** The subject of the Excel Document. */
    char *subject;

    /** The author of the Excel Document. */
    char *author;

    /** The manager field of the Excel Document. */
    char *manager;

    /** The company field of the Excel Document. */
    char *company;

    /** The category of the Excel Document. */
    char *category;

    /** The keywords of the Excel Document. */
    char *keywords;

    /** The comment field of the Excel Document. */
    char *comments;

    /** The status of the Excel Document. */
    char *status;

    /** The hyperlink base URL of the Excel Document. */
    char *hyperlink_base;

    /** The file creation date/time shown in Excel. This defaults to the
     * current time and date if set to 0. If you wish to create files that are
     * binary equivalent (for the same input data) then you should set this
     * creation date/time to a known value. */
    time_t created;

} lxw_doc_properties;

/**
 * @brief Workbook options.
 *
 * Optional parameters when creating a new Workbook object via
 * workbook_new_opt().
 *
 * The following properties are supported:
 *
 * - `constant_memory`: This option reduces the amount of data stored in
 *   memory so that large files can be written efficiently. This option is off
 *   by default. See the note below for limitations when this mode is on.
 *
 * - `tmpdir`: libxlsxwriter stores workbook data in temporary files prior to
 *   assembling the final XLSX file. The temporary files are created in the
 *   system's temp directory. If the default temporary directory isn't
 *   accessible to your application, or doesn't contain enough space, you can
 *   specify an alternative location using the `tmpdir` option.
 *
 * - `use_zip64`: Make the zip library use ZIP64 extensions when writing very
 *   large xlsx files to allow the zip container, or individual XML files
 *   within it, to be greater than 4 GB. See [ZIP64 on Wikipedia][zip64_wiki]
 *   for more information. This option is off by default.
 *
 *   [zip64_wiki]: https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64
 *
 * @note In `constant_memory` mode a row of data is written and then discarded
 * when a cell in a new row is added via one of the `worksheet_write_*()`
 * functions. Therefore, once this option is active, data should be written in
 * sequential row order. For this reason the `worksheet_merge_range()` doesn't
 * work in this mode. See also @ref ww_mem_constant.
 *
 */
typedef struct lxw_workbook_options {
    /** Optimize the workbook to use constant memory for worksheets. */
    uint8_t constant_memory;

    /** Directory to use for the temporary files created by libxlsxwriter. */
    char *tmpdir;

    /** Allow ZIP64 extensions when creating the xlsx file zip container. */
    uint8_t use_zip64;

} lxw_workbook_options;

/**
 * @brief Struct to represent an Excel workbook.
 *
 * The members of the lxw_workbook struct aren't modified directly. Instead
 * the workbook properties are set by calling the functions shown in
 * workbook.h.
 */
typedef struct lxw_workbook {

    FILE *file;
    struct lxw_sheets *sheets;
    struct lxw_worksheets *worksheets;
    struct lxw_chartsheets *chartsheets;
    struct lxw_worksheet_names *worksheet_names;
    struct lxw_chartsheet_names *chartsheet_names;
    struct lxw_image_md5s *image_md5s;
    struct lxw_image_md5s *header_image_md5s;
    struct lxw_charts *charts;
    struct lxw_charts *ordered_charts;
    struct lxw_formats *formats;
    struct lxw_defined_names *defined_names;
    lxw_sst *sst;
    lxw_doc_properties *properties;
    struct lxw_custom_properties *custom_properties;

    char *filename;
    lxw_workbook_options options;

    uint16_t num_sheets;
    uint16_t num_worksheets;
    uint16_t num_chartsheets;
    uint16_t first_sheet;
    uint16_t active_sheet;
    uint16_t num_xf_formats;
    uint16_t num_dxf_formats;
    uint16_t num_format_count;
    uint16_t drawing_count;
    uint16_t comment_count;

    uint16_t font_count;
    uint16_t border_count;
    uint16_t fill_count;
    uint8_t optimize;
    uint16_t max_url_length;

    uint8_t has_png;
    uint8_t has_jpeg;
    uint8_t has_bmp;
    uint8_t has_vml;
    uint8_t has_comments;

    lxw_hash_table *used_xf_formats;
    lxw_hash_table *used_dxf_formats;

    char *vba_project;
    char *vba_codename;

    lxw_format *default_url_format;

} lxw_workbook;


/* *INDENT-OFF* */
#ifdef __cplusplus
extern "C" {
#endif
/* *INDENT-ON* */

/**
 * @brief Create a new workbook object.
 *
 * @param filename The name of the new Excel file to create.
 *
 * @return A lxw_workbook instance.
 *
 * The `%workbook_new()` constructor is used to create a new Excel workbook
 * with a given filename:
 *
 * @code
 *     lxw_workbook *workbook  = workbook_new("filename.xlsx");
 * @endcode
 *
 * When specifying a filename it is recommended that you use an `.xlsx`
 * extension or Excel will generate a warning when opening the file.
 *
 */
lxw_workbook *workbook_new(const char *filename);

/**
 * @brief Create a new workbook object, and set the workbook options.
 *
 * @param filename The name of the new Excel file to create.
 * @param options  Workbook options.
 *
 * @return A lxw_workbook instance.
 *
 * This function is the same as the `workbook_new()` constructor but allows
 * additional options to be set.
 *
 * @code
 *    lxw_workbook_options options = {.constant_memory = LXW_TRUE,
 *                                    .tmpdir = "C:\\Temp",
 *                                    .use_zip64 = LXW_FALSE};
 *
 *    lxw_workbook  *workbook  = workbook_new_opt("filename.xlsx", &options);
 * @endcode
 *
 * The options that can be set via #lxw_workbook_options are:
 *
 * - `constant_memory`: This option reduces the amount of data stored in
 *   memory so that large files can be written efficiently. This option is off
 *   by default. See the note below for limitations when this mode is on.
 *
 * - `tmpdir`: libxlsxwriter stores workbook data in temporary files prior to
 *   assembling the final XLSX file. The temporary files are created in the
 *   system's temp directory. If the default temporary directory isn't
 *   accessible to your application, or doesn't contain enough space, you can
 *   specify an alternative location using the `tmpdir` option.
 *
 * - `use_zip64`: Make the zip library use ZIP64 extensions when writing very
 *   large xlsx files to allow the zip container, or individual XML files
 *   within it, to be greater than 4 GB. See [ZIP64 on Wikipedia][zip64_wiki]
 *   for more information. This option is off by default.
 *
 *   [zip64_wiki]: https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64
 *
 * @note In `constant_memory` mode a row of data is written and then discarded
 * when a cell in a new row is added via one of the `worksheet_write_*()`
 * functions. Therefore, once this option is active, data should be written in
 * sequential row order. For this reason the `worksheet_merge_range()` doesn't
 * work in this mode. See also @ref ww_mem_constant.
 *
 */
lxw_workbook *workbook_new_opt(const char *filename,
                               lxw_workbook_options *options);

/**
 * @brief Add a new worksheet to a workbook.
 *
 * @param workbook  Pointer to a lxw_workbook instance.
 * @param sheetname Optional worksheet name, defaults to Sheet1, etc.
 *
 * @return A lxw_worksheet object.
 *
 * The `%workbook_add_worksheet()` function adds a new worksheet to a workbook.
 *
 * At least one worksheet should be added to a new workbook: The @ref
 * worksheet.h "Worksheet" object is used to write data and configure a
 * worksheet in the workbook.
 *
 * The `sheetname` parameter is optional. If it is `NULL` the default
 * Excel convention will be followed, i.e. Sheet1, Sheet2, etc.:
 *
 * @code
 *     worksheet = workbook_add_worksheet(workbook, NULL  );     // Sheet1
 *     worksheet = workbook_add_worksheet(workbook, "Foglio2");  // Foglio2
 *     worksheet = workbook_add_worksheet(workbook, "Data");     // Data
 *     worksheet = workbook_add_worksheet(workbook, NULL  );     // Sheet4
 *
 * @endcode
 *
 * @image html workbook02.png
 *
 * The worksheet name must be a valid Excel worksheet name, i.e:
 *
 * - The name is less than or equal to 31 UTF-8 characters.
 * - The name doesn't contain any of the characters: ` [ ] : * ? / \ `
 * - The name doesn't start or end with an apostrophe.
 * - The name isn't already in use. (Case insensitive).
 *
 * If any of these errors are encountered the function will return NULL.
 * You can check for valid name using the `workbook_validate_sheet_name()`
 * function.
 *
 * @note You should also avoid using the worksheet name "History" (case
 * insensitive) which is reserved in English language versions of
 * Excel. Non-English versions may have restrictions on the equivalent word.
 */
lxw_worksheet *workbook_add_worksheet(lxw_workbook *workbook,
                                      const char *sheetname);

/**
 * @brief Add a new chartsheet to a workbook.
 *
 * @param workbook  Pointer to a lxw_workbook instance.
 * @param sheetname Optional chartsheet name, defaults to Chart1, etc.
 *
 * @return A lxw_chartsheet object.
 *
 * The `%workbook_add_chartsheet()` function adds a new chartsheet to a
 * workbook. The @ref chartsheet.h "Chartsheet" object is like a worksheet
 * except it displays a chart instead of cell data.
 *
 * @image html chartsheet.png
 *
 * The `sheetname` parameter is optional. If it is `NULL` the default
 * Excel convention will be followed, i.e. Chart1, Chart2, etc.:
 *
 * @code
 *     chartsheet = workbook_add_chartsheet(workbook, NULL  );     // Chart1
 *     chartsheet = workbook_add_chartsheet(workbook, "My Chart"); // My Chart
 *     chartsheet = workbook_add_chartsheet(workbook, NULL  );     // Chart3
 *
 * @endcode
 *
 * The chartsheet name must be a valid Excel worksheet name, i.e.:
 *
 * - The name is less than or equal to 31 UTF-8 characters.
 * - The name doesn't contain any of the characters: ` [ ] : * ? / \ `
 * - The name doesn't start or end with an apostrophe.
 * - The name isn't already in use. (Case insensitive).
 *
 * If any of these errors are encountered the function will return NULL.
 * You can check for valid name using the `workbook_validate_sheet_name()`
 * function.
 *
 * @note You should also avoid using the worksheet name "History" (case
 * insensitive) which is reserved in English language versions of
 * Excel. Non-English versions may have restrictions on the equivalent word.
 *
 * At least one worksheet should be added to a new workbook when creating a
 * chartsheet in order to provide data for the chart. The @ref worksheet.h
 * "Worksheet" object is used to write data and configure a worksheet in the
 * workbook.
 */
lxw_chartsheet *workbook_add_chartsheet(lxw_workbook *workbook,
                                        const char *sheetname);

/**
 * @brief Create a new @ref format.h "Format" object to formats cells in
 *        worksheets.
 *
 * @param workbook Pointer to a lxw_workbook instance.
 *
 * @return A lxw_format instance.
 *
 * The `workbook_add_format()` function can be used to create new @ref
 * format.h "Format" objects which are used to apply formatting to a cell.
 *
 * @code
 *    // Create the Format.
 *    lxw_format *format = workbook_add_format(workbook);
 *
 *    // Set some of the format properties.
 *    format_set_bold(format);
 *    format_set_font_color(format, LXW_COLOR_RED);
 *
 *    // Use the format to change the text format in a cell.
 *    worksheet_write_string(worksheet, 0, 0, "Hello", format);
 * @endcode
 *
 * See @ref format.h "the Format object" and @ref working_with_formats
 * sections for more details about Format properties and how to set them.
 *
 */
lxw_format *workbook_add_format(lxw_workbook *workbook);

/**
 * @brief Create a new chart to be added to a worksheet:
 *
 * @param workbook   Pointer to a lxw_workbook instance.
 * @param chart_type The type of chart to be created. See #lxw_chart_type.
 *
 * @return A lxw_chart object.
 *
 * The `%workbook_add_chart()` function creates a new chart object that can
 * be added to a worksheet:
 *
 * @code
 *     // Create a chart object.
 *     lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_COLUMN);
 *
 *     // Add data series to the chart.
 *     chart_add_series(chart, NULL, "Sheet1!$A$1:$A$5");
 *     chart_add_series(chart, NULL, "Sheet1!$B$1:$B$5");
 *     chart_add_series(chart, NULL, "Sheet1!$C$1:$C$5");
 *
 *     // Insert the chart into the worksheet
 *     worksheet_insert_chart(worksheet, CELL("B7"), chart);
 * @endcode
 *
 * The available chart types are defined in #lxw_chart_type. The types of
 * charts that are supported are:
 *
 * | Chart type                               | Description                            |
 * | :--------------------------------------- | :------------------------------------  |
 * | #LXW_CHART_AREA                          | Area chart.                            |
 * | #LXW_CHART_AREA_STACKED                  | Area chart - stacked.                  |
 * | #LXW_CHART_AREA_STACKED_PERCENT          | Area chart - percentage stacked.       |
 * | #LXW_CHART_BAR                           | Bar chart.                             |
 * | #LXW_CHART_BAR_STACKED                   | Bar chart - stacked.                   |
 * | #LXW_CHART_BAR_STACKED_PERCENT           | Bar chart - percentage stacked.        |
 * | #LXW_CHART_COLUMN                        | Column chart.                          |
 * | #LXW_CHART_COLUMN_STACKED                | Column chart - stacked.                |
 * | #LXW_CHART_COLUMN_STACKED_PERCENT        | Column chart - percentage stacked.     |
 * | #LXW_CHART_DOUGHNUT                      | Doughnut chart.                        |
 * | #LXW_CHART_LINE                          | Line chart.                            |
 * | #LXW_CHART_LINE_STACKED                  | Line chart - stacked.                  |
 * | #LXW_CHART_LINE_STACKED_PERCENT          | Line chart - percentage stacked.       |
 * | #LXW_CHART_PIE                           | Pie chart.                             |
 * | #LXW_CHART_SCATTER                       | Scatter chart.                         |
 * | #LXW_CHART_SCATTER_STRAIGHT              | Scatter chart - straight.              |
 * | #LXW_CHART_SCATTER_STRAIGHT_WITH_MARKERS | Scatter chart - straight with markers. |
 * | #LXW_CHART_SCATTER_SMOOTH                | Scatter chart - smooth.                |
 * | #LXW_CHART_SCATTER_SMOOTH_WITH_MARKERS   | Scatter chart - smooth with markers.   |
 * | #LXW_CHART_RADAR                         | Radar chart.                           |
 * | #LXW_CHART_RADAR_WITH_MARKERS            | Radar chart - with markers.            |
 * | #LXW_CHART_RADAR_FILLED                  | Radar chart - filled.                  |
 *
 *
 *
 * See @ref chart.h for details.
 */
lxw_chart *workbook_add_chart(lxw_workbook *workbook, uint8_t chart_type);

/**
 * @brief Close the Workbook object and write the XLSX file.
 *
 * @param workbook Pointer to a lxw_workbook instance.
 *
 * @return A #lxw_error.
 *
 * The `%workbook_close()` function closes a Workbook object, writes the Excel
 * file to disk, frees any memory allocated internally to the Workbook and
 * frees the object itself.
 *
 * @code
 *     workbook_close(workbook);
 * @endcode
 *
 * The `%workbook_close()` function returns any #lxw_error error codes
 * encountered when creating the Excel file. The error code can be returned
 * from the program main or the calling function:
 *
 * @code
 *     return workbook_close(workbook);
 * @endcode
 *
 */
lxw_error workbook_close(lxw_workbook *workbook);

/**
 * @brief Set the document properties such as Title, Author etc.
 *
 * @param workbook   Pointer to a lxw_workbook instance.
 * @param properties Document properties to set.
 *
 * @return A #lxw_error.
 *
 * The `%workbook_set_properties` function can be used to set the document
 * properties of the Excel file created by `libxlsxwriter`. These properties
 * are visible when you use the `Office Button -> Prepare -> Properties`
 * option in Excel and are also available to external applications that read
 * or index windows files.
 *
 * The properties that can be set are:
 *
 * - `title`
 * - `subject`
 * - `author`
 * - `manager`
 * - `company`
 * - `category`
 * - `keywords`
 * - `comments`
 * - `hyperlink_base`
 * - `created`
 *
 * The properties are specified via a `lxw_doc_properties` struct. All the
 * fields are all optional. An example of how to create and pass the
 * properties is:
 *
 * @code
 *     // Create a properties structure and set some of the fields.
 *     lxw_doc_properties properties = {
 *         .title    = "This is an example spreadsheet",
 *         .subject  = "With document properties",
 *         .author   = "John McNamara",
 *         .manager  = "Dr. Heinz Doofenshmirtz",
 *         .company  = "of Wolves",
 *         .category = "Example spreadsheets",
 *         .keywords = "Sample, Example, Properties",
 *         .comments = "Created with libxlsxwriter",
 *         .status   = "Quo",
 *     };
 *
 *     // Set the properties in the workbook.
 *     workbook_set_properties(workbook, &properties);
 * @endcode
 *
 * @image html doc_properties.png
 *
 * The `created` parameter sets the file creation date/time shown in
 * Excel. This defaults to the current time and date if set to 0. If you wish
 * to create files that are binary equivalent (for the same input data) then
 * you should set this creation date/time to a known value using a `time_t`
 * value.
 *
 */
lxw_error workbook_set_properties(lxw_workbook *workbook,
                                  lxw_doc_properties *properties);

/**
 * @brief Set a custom document text property.
 *
 * @param workbook Pointer to a lxw_workbook instance.
 * @param name     The name of the custom property.
 * @param value    The value of the custom property.
 *
 * @return A #lxw_error.
 *
 * The `%workbook_set_custom_property_string()` function can be used to set one
 * or more custom document text properties not covered by the standard
 * properties in the `workbook_set_properties()` function above.
 *
 *  For example:
 *
 * @code
 *     workbook_set_custom_property_string(workbook, "Checked by", "Eve");
 * @endcode
 *
 * @image html custom_properties.png
 *
 * There are 4 `workbook_set_custom_property_string_*()` functions for each
 * of the custom property types supported by Excel:
 *
 * - text/string: `workbook_set_custom_property_string()`
 * - number:      `workbook_set_custom_property_number()`
 * - datetime:    `workbook_set_custom_property_datetime()`
 * - boolean:     `workbook_set_custom_property_boolean()`
 *
 * **Note**: the name and value parameters are limited to 255 characters
 * by Excel.
 *
 */
lxw_error workbook_set_custom_property_string(lxw_workbook *workbook,
                                              const char *name,
                                              const char *value);
/**
 * @brief Set a custom document number property.
 *
 * @param workbook Pointer to a lxw_workbook instance.
 * @param name     The name of the custom property.
 * @param value    The value of the custom property.
 *
 * @return A #lxw_error.
 *
 * Set a custom document number property.
 * See `workbook_set_custom_property_string()` above for details.
 *
 * @code
 *     workbook_set_custom_property_number(workbook, "Document number", 12345);
 * @endcode
 */
lxw_error workbook_set_custom_property_number(lxw_workbook *workbook,
                                              const char *name, double value);

/* Undocumented since the user can use workbook_set_custom_property_number().
 * Only implemented for file format completeness and testing.
 */
lxw_error workbook_set_custom_property_integer(lxw_workbook *workbook,
                                               const char *name,
                                               int32_t value);

/**
 * @brief Set a custom document boolean property.
 *
 * @param workbook Pointer to a lxw_workbook instance.
 * @param name     The name of the custom property.
 * @param value    The value of the custom property.
 *
 * @return A #lxw_error.
 *
 * Set a custom document boolean property.
 * See `workbook_set_custom_property_string()` above for details.
 *
 * @code
 *     workbook_set_custom_property_boolean(workbook, "Has Review", 1);
 * @endcode
 */
lxw_error workbook_set_custom_property_boolean(lxw_workbook *workbook,
                                               const char *name,
                                               uint8_t value);
/**
 * @brief Set a custom document date or time property.
 *
 * @param workbook Pointer to a lxw_workbook instance.
 * @param name     The name of the custom property.
 * @param datetime The value of the custom property.
 *
 * @return A #lxw_error.
 *
 * Set a custom date or time number property.
 * See `workbook_set_custom_property_string()` above for details.
 *
 * @code
 *     lxw_datetime datetime  = {2016, 12, 1,  11, 55, 0.0};
 *
 *     workbook_set_custom_property_datetime(workbook, "Date completed", &datetime);
 * @endcode
 */
lxw_error workbook_set_custom_property_datetime(lxw_workbook *workbook,
                                                const char *name,
                                                lxw_datetime *datetime);

/**
 * @brief Create a defined name in the workbook to use as a variable.
 *
 * @param workbook Pointer to a lxw_workbook instance.
 * @param name     The defined name.
 * @param formula  The cell or range that the defined name refers to.
 *
 * @return A #lxw_error.
 *
 * This function is used to defined a name that can be used to represent a
 * value, a single cell or a range of cells in a workbook: These defined names
 * can then be used in formulas:
 *
 * @code
 *     workbook_define_name(workbook, "Exchange_rate", "=0.96");
 *     worksheet_write_formula(worksheet, 2, 1, "=Exchange_rate", NULL);
 *
 * @endcode
 *
 * @image html defined_name.png
 *
 * As in Excel a name defined like this is "global" to the workbook and can be
 * referred to from any worksheet:
 *
 * @code
 *     // Global workbook name.
 *     workbook_define_name(workbook, "Sales", "=Sheet1!$G$1:$H$10");
 * @endcode
 *
 * It is also possible to define a local/worksheet name by prefixing it with
 * the sheet name using the syntax `'sheetname!definedname'`:
 *
 * @code
 *     // Local worksheet name.
 *     workbook_define_name(workbook, "Sheet2!Sales", "=Sheet2!$G$1:$G$10");
 * @endcode
 *
 * If the sheet name contains spaces or special characters you must follow the
 * Excel convention and enclose it in single quotes:
 *
 * @code
 *     workbook_define_name(workbook, "'New Data'!Sales", "=Sheet2!$G$1:$G$10");
 * @endcode
 *
 * The rules for names in Excel are explained in the
 * [Microsoft Office documentation](http://office.microsoft.com/en-001/excel-help/define-and-use-names-in-formulas-HA010147120.aspx).
 *
 */
lxw_error workbook_define_name(lxw_workbook *workbook, const char *name,
                               const char *formula);

/**
 * @brief Get the default URL format used with `worksheet_write_url()`.
 *
 * @param  workbook Pointer to a lxw_workbook instance.
 * @return A lxw_format instance that has hyperlink properties set.
 *
 * This function returns a lxw_format instance that is used for the default
 * blue underline hyperlink in the `worksheet_write_url()` function when a
 * format isn't specified:
 *
 * @code
 *     lxw_format *url_format = workbook_get_default_url_format(workbook);
 * @endcode
 *
 * The format is the hyperlink style defined by Excel for the default theme.
 * This format is only ever required when overwriting a string URL with
 * data of a different type. See the example below.
 */
lxw_format *workbook_get_default_url_format(lxw_workbook *workbook);

/**
 * @brief Get a worksheet object from its name.
 *
 * @param workbook Pointer to a lxw_workbook instance.
 * @param name     Worksheet name.
 *
 * @return A lxw_worksheet object.
 *
 * This function returns a lxw_worksheet object reference based on its name:
 *
 * @code
 *     worksheet = workbook_get_worksheet_by_name(workbook, "Sheet1");
 * @endcode
 *
 */
lxw_worksheet *workbook_get_worksheet_by_name(lxw_workbook *workbook,
                                              const char *name);

/**
 * @brief Get a chartsheet object from its name.
 *
 * @param workbook Pointer to a lxw_workbook instance.
 * @param name     chartsheet name.
 *
 * @return A lxw_chartsheet object.
 *
 * This function returns a lxw_chartsheet object reference based on its name:
 *
 * @code
 *     chartsheet = workbook_get_chartsheet_by_name(workbook, "Chart1");
 * @endcode
 *
 */
lxw_chartsheet *workbook_get_chartsheet_by_name(lxw_workbook *workbook,
                                                const char *name);

/**
 * @brief Validate a worksheet or chartsheet name.
 *
 * @param workbook  Pointer to a lxw_workbook instance.
 * @param sheetname Sheet name to validate.
 *
 * @return A #lxw_error.
 *
 * This function is used to validate a worksheet or chartsheet name according
 * to the rules used by Excel:
 *
 * - The name is less than or equal to 31 UTF-8 characters.
 * - The name doesn't contain any of the characters: ` [ ] : * ? / \ `
 * - The name doesn't start or end with an apostrophe.
 * - The name isn't already in use. (Case insensitive, see the note below).
 *
 * @code
 *     lxw_error err = workbook_validate_sheet_name(workbook, "Foglio");
 * @endcode
 *
 * This function is called by `workbook_add_worksheet()` and
 * `workbook_add_chartsheet()` but it can be explicitly called by the user
 * beforehand to ensure that the sheet name is valid.
 *
 * @note You should also avoid using the worksheet name "History" (case
 * insensitive) which is reserved in English language versions of
 * Excel. Non-English versions may have restrictions on the equivalent word.
 *
 * @note This function does an ASCII lowercase string comparison to determine
 * if the sheet name is already in use. It doesn't take UTF-8 characters into
 * account. Thus it would flag "Café" and "café" as a duplicate (just like
 * Excel) but it wouldn't catch "CAFÉ". If you need a full UTF-8 case
 * insensitive check you should use a third party library to implement it.
 *
 */
lxw_error workbook_validate_sheet_name(lxw_workbook *workbook,
                                       const char *sheetname);

/**
 * @brief Add a vbaProject binary to the Excel workbook.
 *
 * @param workbook Pointer to a lxw_workbook instance.
 * @param filename The path/filename of the vbaProject.bin file.
 *
 * The `%workbook_add_vba_project()` function can be used to add macros or
 * functions to a workbook using a binary VBA project file that has been
 * extracted from an existing Excel xlsm file:
 *
 * @code
 *     workbook_add_vba_project(workbook, "vbaProject.bin");
 * @endcode
 *
 * Only one `vbaProject.bin file` can be added per workbook. The name doesn't
 * have to be `vbaProject.bin`. Any suitable path/name for an existing VBA bin
 * file will do.
 *
 * Once you add a VBA project had been add to an libxlsxwriter workbook you
 * should ensure that the file extension is `.xlsm` to prevent Excel from
 * giving a warning when it opens the file:
 *
 * @code
 *     lxw_workbook *workbook = new_workbook("macro.xlsm");
 * @endcode
 *
 * See also @ref working_with_macros
 *
 * @return A #lxw_error.
 */
lxw_error workbook_add_vba_project(lxw_workbook *workbook,
                                   const char *filename);

/**
 * @brief Set the VBA name for the workbook.
 *
 * @param workbook Pointer to a lxw_workbook instance.
 * @param name     Name of the workbook used by VBA.
 *
 * The `workbook_set_vba_name()` function can be used to set the VBA name for
 * the workbook. This is sometimes required when a vbaProject macro included
 * via `workbook_add_vba_project()` refers to the workbook by a name other
 * than `ThisWorkbook`.
 *
 * @code
 *     workbook_set_vba_name(workbook, "MyWorkbook");
 * @endcode
 *
 * If an Excel VBA name for the workbook isn't specified then libxlsxwriter
 * will use `ThisWorkbook`.
 *
 * See also @ref working_with_macros
 *
 * @return A #lxw_error.
 */
lxw_error workbook_set_vba_name(lxw_workbook *workbook, const char *name);

void lxw_workbook_free(lxw_workbook *workbook);
void lxw_workbook_assemble_xml_file(lxw_workbook *workbook);
void lxw_workbook_set_default_xf_indices(lxw_workbook *workbook);
void workbook_unset_default_url_format(lxw_workbook *workbook);

/* Declarations required for unit testing. */
#ifdef TESTING

STATIC void _workbook_xml_declaration(lxw_workbook *self);
STATIC void _write_workbook(lxw_workbook *self);
STATIC void _write_file_version(lxw_workbook *self);
STATIC void _write_workbook_pr(lxw_workbook *self);
STATIC void _write_book_views(lxw_workbook *self);
STATIC void _write_workbook_view(lxw_workbook *self);
STATIC void _write_sheet(lxw_workbook *self,
                         const char *name, uint32_t sheet_id, uint8_t hidden);
STATIC void _write_sheets(lxw_workbook *self);
STATIC void _write_calc_pr(lxw_workbook *self);

STATIC void _write_defined_name(lxw_workbook *self,
                                lxw_defined_name *define_name);
STATIC void _write_defined_names(lxw_workbook *self);

STATIC lxw_error _store_defined_name(lxw_workbook *self, const char *name,
                                     const char *app_name,
                                     const char *formula, int16_t index,
                                     uint8_t hidden);

#endif /* TESTING */

/* *INDENT-OFF* */
#ifdef __cplusplus
}
#endif
/* *INDENT-ON* */

#endif /* __LXW_WORKBOOK_H__ */
