/*
 * Copyright (c) 2001-2002  Ben Fennema
 * Copyright (c) 2016-2019  Pali Rohár <pali.rohar@gmail.com>
 * All rights reserved.
 * Copyright (C) 2020 ~ 2021 Uniontech Software Technology Co., Ltd.
 *
 * Author:     zhangsheng<zhangsheng@uniontech.com>
 *
 * Maintainer: max-lv<lvwujun@uniontech.com>
 *             xushitong<xushitong@uniontech.com>
 *             dengkeyun<dengkeyun@uniontech.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */
#ifndef UDFFS_H
#define UDFFS_H

#include <stddef.h>
#include <linux/limits.h>

#include "ecma_167.h"
#include "osta_udf.h"
#include "bswap.h"

#define MAX_FD_COUNT            1024
#define INCREASE_FILE_COUNT     1024

#define FLAG_FREED_BITMAP       0x00000001
#define FLAG_FREED_TABLE        0x00000002
#define FLAG_UNALLOC_BITMAP     0x00000004
#define FLAG_UNALLOC_TABLE      0x00000008
#define FLAG_SPACE_BITMAP       (FLAG_FREED_BITMAP|FLAG_UNALLOC_BITMAP)
#define FLAG_SPACE_TABLE        (FLAG_FREED_TABLE|FLAG_UNALLOC_TABLE)
#define FLAG_SPACE              (FLAG_SPACE_BITMAP|FLAG_SPACE_TABLE)

#define FLAG_LOCALE             0x00000010
#define FLAG_UNICODE8           0x00000020
#define FLAG_UNICODE16          0x00000040
#define FLAG_UTF8               0x00000080
#define FLAG_CHARSET            (FLAG_LOCALE|FLAG_UNICODE8|FLAG_UNICODE16|FLAG_UTF8)

#define FLAG_MULTISESSION       0x00001000

#define FLAG_EFE                0x00002000

struct udf_extent_;
struct udf_desc_;
struct udf_data_;
struct udf_file_;
typedef struct _dir_ Directory;

struct udf_sizing
{
    uint32_t	align;
    uint32_t	numSize;
    uint32_t	denomSize;
    uint32_t	minSize;
};

enum udf_alloc_type
{
    VDS_SIZE,
    LVID_SIZE,
    STABLE_SIZE,
    SSPACE_SIZE,
    PSPACE_SIZE,
    UDF_ALLOC_TYPE_SIZE,
};

enum udf_space_type
{
    RESERVED    = 0x0001,	/* Reserved Space */
    VRS         = 0x0002,	/* Volume Recognition Sequence */
    ANCHOR      = 0x0004,	/* Anchor */
    MVDS        = 0x0008,	/* Main Volume Descriptor Sequence */
    RVDS        = 0x0010,	/* Reserve Volume Descriptor Sequence */
    LVID        = 0x0020,	/* Logical Volume Integrity Descriptor */
    STABLE      = 0x0040,	/* Sparing Table */
    SSPACE      = 0x0080,	/* Sparing Space */
    PSPACE      = 0x0100,	/* Partition Space */
    USPACE      = 0x0200,	/* Unallocated Space */
    BAD         = 0x0400,	/* Bad Blocks */
    MBR         = 0x0800,	/* MBR Boot Area */
    UDF_SPACE_TYPE_SIZE = 12,
};


typedef struct udf_image_ {
    int                             fd;
    int                             index;
    char                            pathname[PATH_MAX];            // absloute image file path
    char                            devname[NAME_MAX + 1];         // use by multisession
    uint32_t                        max_file_count;                // like capacity of std::vector
    uint32_t                        cur_file_count;                // like size of std::vector
    struct udf_file_                *inner_files;                  // TODO: sort

    uint16_t                        udf_rev;            // must 0x0102 now
    uint32_t                        blocksize;          // 2048 in disc
    uint32_t                        blocks;             // all blocks in the image
    uint32_t                        flags;
    uint32_t                        last_sess_start;
    uint32_t                        next_sess_start;
    uint32_t                        num_files;          // use it when parse multisession
    uint32_t                        num_dirs;           // use it when parse multisession

    uint32_t                        uid;
    uint32_t                        gid;
    uint16_t                        mode;

    struct udf_sizing               sizing[UDF_ALLOC_TYPE_SIZE];

    uint64_t                        (*total_size)(const struct udf_image_ *, uint64_t *);
    int                             (*write)(struct udf_image_ *, struct udf_extent_ *);
    int                             (*fake_write)(struct udf_image_ *, struct udf_extent_ *);

    struct volStructDesc            *udf_vrs[3];
    struct anchorVolDescPtr         *udf_anchor[3];
    struct primaryVolDesc           *udf_pvd[2];
    struct logicalVolDesc           *udf_lvd[2];
    struct partitionDesc            *udf_pd[2];
    struct unallocSpaceDesc         *udf_usd[2];
    struct impUseVolDesc            *udf_iuvd[2];
    struct terminatingDesc          *udf_td[2];
    struct logicalVolIntegrityDesc	*udf_lvid;

    struct fileSetDesc              *udf_fsd;

    struct udf_extent_              *head;
    struct udf_extent_              *tail;
} udf_image;

typedef struct udf_extent_
{
    enum udf_space_type		space_type;
    uint32_t                start;
    uint32_t                blocks;

    struct udf_desc_		*head;
    struct udf_desc_		*tail;

    struct udf_extent_		*next;
    struct udf_extent_		*prev;
} udf_extent;

typedef struct udf_desc_
{
    uint16_t            ident;
    uint32_t            offset;
    uint64_t            length;
    struct udf_data_    *data;

    uint32_t            fcount; /* TODO: extract */
    struct udf_file_    *file;  /* TODO: extract */

    struct udf_desc_    *next;
    struct udf_desc_    *prev;
} udf_desc;

typedef struct udf_data_
{
    uint64_t            length;
    void                *buffer;
    struct udf_data_    *next;
    struct udf_data_    *prev;
} udf_data;

typedef struct udf_file_ {
    char *file_name;
    dchars uname[NAME_MAX];
    uint8_t ulen;
    struct fileEntry *fe;
    udf_data *fid_data;
} udf_file;

typedef struct {
    int count;
    udf_image *img_array[MAX_FD_COUNT];
} images_context;

#define UDF_ID_APPLICATION		"*ZXLL"
#define UDF_ID_DEVELOPER		"*ZXLL"

#define DEFAULT_HD	0
#define DEFAULT_CD	0
#define DEFAULT_CDR	0
#define DEFAULT_DVD	0
#define DEFAULT_DVDRAM	0
#define DEFAULT_WORM	1
#define DEFAULT_MO	1
#define DEFAULT_CDRW	2
#define DEFAULT_BDR	3
#define DEFAULT_DVDRW	4
#define DEFAULT_DVDR	5

enum {
    UDFFS_OK = 0,
    UDFFS_MEMORY_ALLOC_FAILED,
    UDFFS_INVALID_VERSION,
    UDFFS_INVALID_DEVICE,
    UDFFS_SPACE_ERROR,
    UDFFS_SETUP_ERROR,
    UDFFS_WRITE_ERROR,
};

enum media_type {
    MEDIA_TYPE_NONE,
    MEDIA_TYPE_HD,
    MEDIA_TYPE_DVD,
    MEDIA_TYPE_DVDRAM,
    MEDIA_TYPE_DVDRW,
    MEDIA_TYPE_DVDR,
    MEDIA_TYPE_WORM,
    MEDIA_TYPE_MO,
    MEDIA_TYPE_CDRW,
    MEDIA_TYPE_CDR,
    MEDIA_TYPE_CD,
    MEDIA_TYPE_BDR,
};

extern char *udf_space_type_str[UDF_SPACE_TYPE_SIZE];

void udf_image_dump(udf_image *);
int udf_image_init(udf_image *);
int udf_set_version(udf_image *, uint16_t);
void udf_set_blocks(udf_image *);
int udf_split_space(udf_image *);

int udf_setup_all(udf_image *, udf_image *, Directory *);
int udf_setup_vrs(udf_image *);
int udf_setup_anchor(udf_image *);
int udf_setup_partion(udf_image *, udf_image *, Directory *);
int udf_setup_vds(udf_image *);
int udf_setup_space(udf_image *, udf_extent *, uint32_t);
int udf_setup_fileset(udf_image *, udf_extent *);
int udf_setup_root(udf_image *, udf_extent *);
int udf_setup_files(udf_image *, udf_extent *, udf_image *, Directory *);
void udf_setup_pvd(udf_image *, udf_extent *, udf_extent *, uint32_t);
void udf_setup_lvd(udf_image *, udf_extent *, udf_extent *,  udf_extent *, uint32_t);
void udf_setup_pd(udf_image *, udf_extent *, udf_extent *, uint32_t);
void udf_setup_usd(udf_image *, udf_extent *, udf_extent *, uint32_t);
void udf_setup_iuvd(udf_image *, udf_extent *, udf_extent *, uint32_t);
void udf_setup_td(udf_image *, udf_extent *, udf_extent *, uint32_t);
void udf_setup_lvid(udf_image *, udf_extent *);

int udf_write_image(udf_image *);
void udf_add_type1_partition(udf_image *, uint16_t);

/* context.c */
enum {
    CONTEXT_OK = 0,
    CONTEXT_FULL,
    CONTEXT_EMPTY,
    CONTEXT_NO_CHANGE
};

images_context *context_get_default();
void context_init(images_context *);
int context_append(images_context *, udf_image *);
int context_remove(images_context *, udf_image *);
udf_image *context_query_image(images_context *, int );
int context_get_fd(const udf_image *);
int context_get_index(const udf_image *);

/* unicode.c */
extern size_t decode_utf8(const dchars *, char *, size_t, size_t);
extern size_t encode_utf8(dchars *, const char *, size_t);
extern size_t decode_locale(const dchars *, char *, size_t, size_t);
extern size_t encode_locale(dchars *, const char *, size_t);
extern size_t decode_string(udf_image *, const dstring *, char *, size_t, size_t);
extern size_t encode_string(udf_image *, dstring *, const char *, size_t);

/* crc.c */
extern uint16_t udf_crc(uint8_t *, uint32_t, uint16_t);

/* extent.c */
udf_extent*next_extent(udf_extent*, enum udf_space_type);
uint32_t next_extent_size(udf_image *, udf_extent*, enum udf_space_type, uint32_t, uint32_t);
uint32_t find_next_extent_size(udf_image *, uint32_t, enum udf_space_type, uint32_t, uint32_t);
udf_extent *prev_extent(udf_extent*, enum udf_space_type);
uint32_t prev_extent_size(udf_extent*, enum udf_space_type, uint32_t, uint32_t);
udf_extent *find_extent(udf_image *, uint32_t);
udf_extent *set_extent(udf_image *, enum udf_space_type, uint32_t,uint32_t);
void remove_extent(udf_image *, udf_extent*);
udf_desc *next_desc(udf_desc*, uint16_t);
udf_desc *find_desc(udf_extent*, uint32_t);
udf_desc *set_desc(udf_extent*, uint16_t, uint32_t, uint32_t, udf_data*);
void append_data(udf_desc*, udf_data*);
udf_data *alloc_data(void *, int);

/* misc.c */
size_t gen_uuid_from_vol_set_ident(char[17], const dstring *, size_t);
uint32_t strtou32(const char *, int, int *);
uint16_t strtou16(const char *, int, int *);
uint32_t randu32(void);
ssize_t read_nointr(int, void *, size_t);
ssize_t write_nointr(int, const void *, size_t);

#endif // UDFFS_H
