// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
*
*   Copyright (C) 2000-2015, International Business Machines
*   Corporation and others.  All Rights Reserved.
*
*******************************************************************************
*
* File reslist.h
*
* Modification History:
*
*   Date        Name        Description
*   02/21/00    weiv        Creation.
*******************************************************************************
*/

#ifndef RESLIST_H
#define RESLIST_H

#define KEY_SPACE_SIZE 65536
#define RESLIST_INT_VECTOR_INIT_SIZE 2048

#include <functional>

#include "unicode/utypes.h"
#include "unicode/unistr.h"
#include "unicode/ures.h"
#include "unicode/ustring.h"
#include "cmemory.h"
#include "cstring.h"
#include "uhash.h"
#include "unewdata.h"
#include "uresdata.h"
#include "ustr.h"

U_CDECL_BEGIN

class PathFilter;
class PseudoListResource;
class ResKeyPath;

struct ResFile {
    ResFile()
            : fBytes(nullptr), fIndexes(nullptr),
              fKeys(nullptr), fKeysLength(0), fKeysCount(0),
              fStrings(nullptr), fStringIndexLimit(0),
              fChecksum(0) {}
    ~ResFile() { close(); }

    void close();

    uint8_t *fBytes;
    const int32_t *fIndexes;
    const char *fKeys;
    int32_t fKeysLength;
    int32_t fKeysCount;

    PseudoListResource *fStrings;
    int32_t fStringIndexLimit;

    int32_t fChecksum;
};

struct SResource;

typedef struct KeyMapEntry {
    int32_t oldpos, newpos;
} KeyMapEntry;

/* Resource bundle root table */
struct SRBRoot {
    SRBRoot(const UString *comment, UBool isPoolBundle, UErrorCode &errorCode);
    ~SRBRoot();

    void write(const char *outputDir, const char *outputPkg,
               char *writtenFilename, int writtenFilenameLen, UErrorCode &errorCode);

    void setLocale(char16_t *locale, UErrorCode &errorCode);
    int32_t addTag(const char *tag, UErrorCode &errorCode);

    const char *getKeyString(int32_t key) const;
    const char *getKeyBytes(int32_t *pLength) const;

    int32_t addKeyBytes(const char *keyBytes, int32_t length, UErrorCode &errorCode);

    void compactKeys(UErrorCode &errorCode);

    int32_t makeRes16(uint32_t resWord) const;
    int32_t mapKey(int32_t oldpos) const;

private:
    void compactStringsV2(UHashtable *stringSet, UErrorCode &errorCode);

public:
    // TODO: private

  SResource *fRoot;  // Normally a TableResource.
  char *fLocale;
  int32_t fIndexLength;
  int32_t fMaxTableLength;
  UBool fNoFallback; /* see URES_ATT_NO_FALLBACK */
  int8_t fStringsForm; /* default STRINGS_UTF16_V1 */
  UBool fIsPoolBundle;

  char *fKeys;
  KeyMapEntry *fKeyMap;
  int32_t fKeysBottom, fKeysTop;
  int32_t fKeysCapacity;
  int32_t fKeysCount;
  int32_t fLocalKeyLimit; /* key offset < limit fits into URES_TABLE */

  icu::UnicodeString f16BitUnits;
  int32_t f16BitStringsLength;

  const ResFile *fUsePoolBundle;
  int32_t fPoolStringIndexLimit;
  int32_t fPoolStringIndex16Limit;
  int32_t fLocalStringIndexLimit;
  SRBRoot *fWritePoolBundle;
};

/* write a java resource file */
// TODO: C++ify
void bundle_write_java(struct SRBRoot *bundle, const char *outputDir, const char* outputEnc, char *writtenFilename, 
                       int writtenFilenameLen, const char* packageName, const char* bundleName, UErrorCode *status);

/* write a xml resource file */
// TODO: C++ify
void bundle_write_xml(struct SRBRoot *bundle, const char *outputDir,const char* outputEnc, const char* rbname,
                  char *writtenFilename, int writtenFilenameLen, const char* language, const char* package, UErrorCode *status);

/* Various resource types */

/*
 * Return a unique pointer to a dummy object,
 * for use in non-error cases when no resource is to be added to the bundle.
 * (nullptr is used in error cases.)
 */
struct SResource* res_none();

class ArrayResource;
class TableResource;
class IntVectorResource;

TableResource *table_open(struct SRBRoot *bundle, const char *tag, const struct UString* comment, UErrorCode *status);

ArrayResource *array_open(struct SRBRoot *bundle, const char *tag, const struct UString* comment, UErrorCode *status);

struct SResource *string_open(struct SRBRoot *bundle, const char *tag, const char16_t *value, int32_t len, const struct UString* comment, UErrorCode *status);

struct SResource *alias_open(struct SRBRoot *bundle, const char *tag, char16_t *value, int32_t len, const struct UString* comment, UErrorCode *status);

IntVectorResource *intvector_open(struct SRBRoot *bundle, const char *tag,  const struct UString* comment, UErrorCode *status);

struct SResource *int_open(struct SRBRoot *bundle, const char *tag, int32_t value, const struct UString* comment, UErrorCode *status);

struct SResource *bin_open(struct SRBRoot *bundle, const char *tag, uint32_t length, uint8_t *data, const char* fileName, const struct UString* comment, UErrorCode *status);

/* Resource place holder */

struct SResource {
    SResource();
    SResource(SRBRoot *bundle, const char *tag, int8_t type, const UString* comment,
              UErrorCode &errorCode);
    virtual ~SResource();

    UBool isTable() const { return fType == URES_TABLE; }
    UBool isString() const { return fType == URES_STRING; }

    const char *getKeyString(const SRBRoot *bundle) const;

    /**
     * Preflights strings.
     * Finds duplicates and counts the total number of string code units
     * so that they can be written first to the 16-bit array,
     * for minimal string and container storage.
     *
     * We walk the final parse tree, rather than collecting this information while building it,
     * so that we need not deal with changes to the parse tree (especially removing resources).
     */
    void preflightStrings(SRBRoot *bundle, UHashtable *stringSet, UErrorCode &errorCode);
    virtual void handlePreflightStrings(SRBRoot *bundle, UHashtable *stringSet, UErrorCode &errorCode);

    /**
     * Writes resource values into f16BitUnits
     * and determines the resource item word, if possible.
     */
    void write16(SRBRoot *bundle);
    virtual void handleWrite16(SRBRoot *bundle);

    /**
     * Calculates ("preflights") and advances the *byteOffset
     * by the size of the resource's data in the binary file and
     * determines the resource item word.
     *
     * Most handlePreWrite() functions may add any number of bytes, but preWrite()
     * will always pad it to a multiple of 4.
     * The resource item type may be a related subtype of the fType.
     *
     * The preWrite() and write() functions start and end at the same
     * byteOffset values.
     * Prewriting allows bundle.write() to determine the root resource item word,
     * before actually writing the bundle contents to the file,
     * which is necessary because the root item is stored at the beginning.
     */
    void preWrite(uint32_t *byteOffset);
    virtual void handlePreWrite(uint32_t *byteOffset);

    /**
     * Writes the resource's data to mem and updates the byteOffset
     * in parallel.
     */
    void write(UNewDataMemory *mem, uint32_t *byteOffset);
    virtual void handleWrite(UNewDataMemory *mem, uint32_t *byteOffset);

    /**
     * Applies the given filter with the given base path to this resource.
     * Removes child resources rejected by the filter recursively.
     * 
     * @param bundle Needed in order to access the key for this and child resources.
     */
    virtual void applyFilter(const PathFilter& filter, ResKeyPath& path, const SRBRoot* bundle);

    /**
     * Calls the given function for every key ID present in this tree.
     */
    virtual void collectKeys(std::function<void(int32_t)> collector) const;

    int8_t   fType;     /* nominal type: fRes (when != 0xffffffff) may use subtype */
    UBool    fWritten;  /* res_write() can exit early */
    uint32_t fRes;      /* resource item word; RES_BOGUS=0xffffffff if not known yet */
    int32_t  fRes16;    /* Res16 version of fRes for Table, Table16, Array16; -1 if it does not fit. */
    int32_t  fKey;      /* Index into bundle->fKeys; -1 if no key. */
    int32_t  fKey16;    /* Key16 version of fKey for Table & Table16; -1 if no key or it does not fit. */
    int      line;      /* used internally to report duplicate keys in tables */
    SResource *fNext;   /* This is for internal chaining while building */
    struct UString fComment;
};

class ContainerResource : public SResource {
public:
    ContainerResource(SRBRoot *bundle, const char *tag, int8_t type,
                      const UString* comment, UErrorCode &errorCode)
            : SResource(bundle, tag, type, comment, errorCode),
              fCount(0), fFirst(nullptr) {}
    virtual ~ContainerResource();

    void handlePreflightStrings(SRBRoot *bundle, UHashtable *stringSet, UErrorCode &errorCode) override;

    void collectKeys(std::function<void(int32_t)> collector) const override;

protected:
    void writeAllRes16(SRBRoot *bundle);
    void preWriteAllRes(uint32_t *byteOffset);
    void writeAllRes(UNewDataMemory *mem, uint32_t *byteOffset);
    void writeAllRes32(UNewDataMemory *mem, uint32_t *byteOffset);

public:
    // TODO: private with getter?
    uint32_t fCount;
    SResource *fFirst;
};

class TableResource : public ContainerResource {
public:
    TableResource(SRBRoot *bundle, const char *tag,
                  const UString* comment, UErrorCode &errorCode)
            : ContainerResource(bundle, tag, URES_TABLE, comment, errorCode),
              fTableType(URES_TABLE), fRoot(bundle) {}
    virtual ~TableResource();

    void add(SResource *res, int linenumber, UErrorCode &errorCode);

    void handleWrite16(SRBRoot *bundle) override;
    void handlePreWrite(uint32_t *byteOffset) override;
    void handleWrite(UNewDataMemory *mem, uint32_t *byteOffset) override;

    void applyFilter(const PathFilter& filter, ResKeyPath& path, const SRBRoot* bundle) override;

    int8_t fTableType;  // determined by table_write16() for table_preWrite() & table_write()
    SRBRoot *fRoot;
};

class ArrayResource : public ContainerResource {
public:
    ArrayResource(SRBRoot *bundle, const char *tag,
                  const UString* comment, UErrorCode &errorCode)
            : ContainerResource(bundle, tag, URES_ARRAY, comment, errorCode),
              fLast(nullptr) {}
    virtual ~ArrayResource();

    void add(SResource *res);

    virtual void handleWrite16(SRBRoot *bundle) override;
    virtual void handlePreWrite(uint32_t *byteOffset) override;
    virtual void handleWrite(UNewDataMemory *mem, uint32_t *byteOffset) override;

    SResource *fLast;
};

/**
 * List of resources for a pool bundle.
 * Writes an empty table resource, rather than a container structure.
 */
class PseudoListResource : public ContainerResource {
public:
    PseudoListResource(SRBRoot *bundle, UErrorCode &errorCode)
            : ContainerResource(bundle, nullptr, URES_TABLE, nullptr, errorCode) {}
    virtual ~PseudoListResource();

    void add(SResource *res);

    virtual void handleWrite16(SRBRoot *bundle) override;
};

class StringBaseResource : public SResource {
public:
    StringBaseResource(SRBRoot *bundle, const char *tag, int8_t type,
                       const char16_t *value, int32_t len,
                       const UString* comment, UErrorCode &errorCode);
    StringBaseResource(SRBRoot *bundle, int8_t type,
                       const icu::UnicodeString &value, UErrorCode &errorCode);
    StringBaseResource(int8_t type, const char16_t *value, int32_t len, UErrorCode &errorCode);
    virtual ~StringBaseResource();

    const char16_t *getBuffer() const { return icu::toUCharPtr(fString.getBuffer()); }
    int32_t length() const { return fString.length(); }

    virtual void handlePreWrite(uint32_t *byteOffset) override;
    virtual void handleWrite(UNewDataMemory *mem, uint32_t *byteOffset) override;

    // TODO: private with getter?
    icu::UnicodeString fString;
};

class StringResource : public StringBaseResource {
public:
    StringResource(SRBRoot *bundle, const char *tag, const char16_t *value, int32_t len,
                   const UString* comment, UErrorCode &errorCode)
            : StringBaseResource(bundle, tag, URES_STRING, value, len, comment, errorCode),
              fSame(nullptr), fSuffixOffset(0),
              fNumCopies(0), fNumUnitsSaved(0), fNumCharsForLength(0) {}
    StringResource(SRBRoot *bundle, const icu::UnicodeString &value, UErrorCode &errorCode)
            : StringBaseResource(bundle, URES_STRING, value, errorCode),
              fSame(nullptr), fSuffixOffset(0),
              fNumCopies(0), fNumUnitsSaved(0), fNumCharsForLength(0) {}
    StringResource(int32_t poolStringIndex, int8_t numCharsForLength,
                   const char16_t *value, int32_t length,
                   UErrorCode &errorCode)
            : StringBaseResource(URES_STRING, value, length, errorCode),
              fSame(nullptr), fSuffixOffset(0),
              fNumCopies(0), fNumUnitsSaved(0), fNumCharsForLength(numCharsForLength) {
        // v3 pool string encoded as string-v2 with low offset
        fRes = URES_MAKE_RESOURCE(URES_STRING_V2, poolStringIndex);
        fWritten = true;
    }
    virtual ~StringResource();

    int32_t get16BitStringsLength() const {
        return fNumCharsForLength + length() + 1;  // +1 for the NUL
    }

    virtual void handlePreflightStrings(SRBRoot *bundle, UHashtable *stringSet, UErrorCode &errorCode) override;
    virtual void handleWrite16(SRBRoot *bundle) override;

    void writeUTF16v2(int32_t base, icu::UnicodeString &dest);

    StringResource *fSame;  // used for duplicates
    int32_t fSuffixOffset;  // this string is a suffix of fSame at this offset
    int32_t fNumCopies;     // number of equal strings represented by one stringSet element
    int32_t fNumUnitsSaved;  // from not writing duplicates and suffixes
    int8_t fNumCharsForLength;
};

class AliasResource : public StringBaseResource {
public:
    AliasResource(SRBRoot *bundle, const char *tag, const char16_t *value, int32_t len,
                  const UString* comment, UErrorCode &errorCode)
            : StringBaseResource(bundle, tag, URES_ALIAS, value, len, comment, errorCode) {}
    virtual ~AliasResource();
};

class IntResource : public SResource {
public:
    IntResource(SRBRoot *bundle, const char *tag, int32_t value,
                const UString* comment, UErrorCode &errorCode);
    virtual ~IntResource();

    // TODO: private with getter?
    int32_t fValue;
};

class IntVectorResource : public SResource {
public:
    IntVectorResource(SRBRoot *bundle, const char *tag,
                      const UString* comment, UErrorCode &errorCode);
    virtual ~IntVectorResource();

    void add(int32_t value, UErrorCode &errorCode);

    virtual void handlePreWrite(uint32_t *byteOffset) override;
    virtual void handleWrite(UNewDataMemory *mem, uint32_t *byteOffset) override;

    // TODO: UVector32
    size_t fCount;
    size_t fSize;
    uint32_t *fArray;
};

class BinaryResource : public SResource {
public:
    BinaryResource(SRBRoot *bundle, const char *tag,
                   uint32_t length, uint8_t *data, const char* fileName,
                   const UString* comment, UErrorCode &errorCode);
    virtual ~BinaryResource();

    virtual void handlePreWrite(uint32_t *byteOffset) override;
    virtual void handleWrite(UNewDataMemory *mem, uint32_t *byteOffset) override;

    // TODO: CharString?
    uint32_t fLength;
    uint8_t *fData;
    // TODO: CharString
    char* fFileName;  // file name for binary or import binary tags if any
};

// TODO: use LocalPointer or delete
void res_close(struct SResource *res);

void setIncludeCopyright(UBool val);
UBool getIncludeCopyright();

void setFormatVersion(int32_t formatVersion);

int32_t getFormatVersion();

void setUsePoolBundle(UBool use);

/* in wrtxml.cpp */
uint32_t computeCRC(const char *ptr, uint32_t len, uint32_t lastcrc);

U_CDECL_END
#endif /* #ifndef RESLIST_H */
