/*
  Copyright ® Olivier Montanuy,
              André Majorel,
              contributors to the DeuTex project.

  DeuTex incorporates code derived from DEU 5.21 that was put in the
  public domain in 1994 by Raphaël Quinet and Brendon Wyber.

  SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "deutex.h"
#include <errno.h>
#include "tools.h"
#include "endianm.h"
#include "text.h"
#include "mkwad.h"
#include "texture.h"
#include "wadio.h"

/* The wad format limits the maximum number of patches per texture. */
#define MAX_PATCHES 32767

/* add new patchs by cluster of 64 */
#define NEWPATCHS                0x40
/* add new textures by cluster of 64 */
#define NEWTEXTURES                0x40
/* more than 8000 textures is unreasonable */
#define MAXTEXTURES                0x2000
/* add new patch defs by cluster of 128*/
#define NEWPATCHESDEF                0x80

/*
  TEXU
  module that stores the textures definitions

  PNM
  modules that stores the patch names
*/

static struct PNMP {
    char name[8];
} *PNMpatchs;
static int16_t PNMtop;
static int16_t PNMmax;
static int16_t PNMknown;
static bool PNMok = false;

void PNMinit(char *buffer, int32_t size)
{
    int16_t n, i;
    char picname[8];
    int32_t pnames;
    /*find the number of entries in PNAME */
    pnames = 0;
    if (size > 4L) {
        pnames = peek_i32_le(buffer);
        if (pnames > 0x7FFFL)
            ProgError("PA01", "Too many patches");
        if (size < (4L + 8L * pnames))
            ProgError("PA02", "PNAMES has wrong size");
    }
    /*initialise */
    PNMmax = (int16_t) (pnames + NEWPATCHS);
    PNMpatchs = (struct PNMP *) Malloc(PNMmax * sizeof(struct PNMP));
    PNMtop = (int16_t) pnames;
    PNMknown = 0;
    /*Read patches */
    if (pnames <= 0)
        return;
    for (n = 0; n < PNMtop; n++) {
        for (i = 0; i < 8; i++)
            picname[i] = buffer[4L + 8L * n + i];
        Normalise(PNMpatchs[n].name, picname);
    }
    PNMknown = PNMtop;
    PNMok = true;
}

/*
** check if PNAME exists (ident.c)
*/
int16_t PNMindexOfPatch(char *patch)
{
    int16_t idx;
    char name[8];
    Normalise(name, patch);
    if (!PNMok)
        return -1;
    /*check index already exists */
    for (idx = 0; idx < PNMtop; idx++)
        if (strncmp(PNMpatchs[idx].name, name, 8) == 0) /*if equal */
            return idx;
    return -1;
}

/* Try to locate a Patch, from name
** if it doesn't exist, add it to the list
*/
static int16_t PNMgetPatchIndex(char *patch)
{
    int16_t idx;
    char name[8];
    if (!PNMok)
        Bug("PA90", "PNMok");
    Normalise(name, patch);
    idx = PNMindexOfPatch(patch);
    if (idx < 0) {              /*No, it's a new patch, then */
        idx = PNMtop;
        Normalise(PNMpatchs[idx].name, name);
        PNMtop++;               /* Increase top of patches */
        if (PNMtop >= PNMmax) {
            PNMmax += NEWPATCHS;
            PNMpatchs =
                (struct PNMP *) Realloc(PNMpatchs,
                                        PNMmax * sizeof(struct PNMP));
        }
    }
    return idx;
}

/*
** get name from index
*/
void PNMgetPatchName(char name[8], int16_t index)
{
    if (!PNMok)
        Bug("PA91", "PNMok");
    if (index >= PNMtop)
        Bug("PA92", "PnmGP>");
    Normalise(name, PNMpatchs[index].name);
}

/*
** Insert in directory all the entries which are not
** referenced in DOOM.WAD
*/
int16_t PNMgetNbOfPatch(void)
{
    return PNMtop;
}

bool PNMisNew(int16_t idx)
{
    if (!PNMok)
        Bug("PA93", "PNMok");
    if (idx >= PNMtop)
        Bug("PA94", "PnmIN>");
    /*check if patch was added after initial definition */
    if (idx >= PNMknown)
        return true;
    return false;
}

void PNMfree(void)
{
    PNMok = false;
    free(PNMpatchs);
}

int32_t PNMwritePNAMEtoWAD(struct WADINFO *info)
{
    int16_t idx;
    int32_t size = 0;
    char buffer[8];
    if (!PNMok)
        Bug("PA95", "PNMok");
    /*Write the Number of entries */
    size += WADRwriteLong(info, PNMtop);
    /*Then write the names , '\0' padded */
    for (idx = 0; idx < PNMtop; idx++) {
        Normalise(buffer, PNMpatchs[idx].name);
        size += WADRwriteBytes(info, buffer, 8);
    }
    return size;
}

struct TEXTUR {
    char Name[8];
    int16_t szX;
    int16_t szY;
    int16_t Npatches;
};
struct PATCH {
    int16_t Pindex;
    int16_t ofsX;
    int16_t ofsY;
};

/*Textures: name, size*/
struct TEXTUR *TXUtex;
int16_t TXUtexCur;              /*currently edited in TEXUtex */
int16_t TXUtexTop;              /*top of in TEXUtex */
int16_t TXUtexMax;
/*Textures: patches composing textures*/
struct PATCH *TXUpat;
long TXUpatTop;                 /*top of TEXUpat */
long TXUpatMax;

bool TXUok = false;

void TXUinit(void)
{
    TXUtexMax = NEWTEXTURES;
    TXUtexTop = 0;
    TXUtex = (struct TEXTUR *) Malloc(TXUtexMax * sizeof(struct TEXTUR));
    TXUpatMax = NEWPATCHESDEF;
    TXUpatTop = 0;
    TXUpat = (struct PATCH *) Malloc(TXUpatMax * sizeof(struct PATCH));
    TXUok = true;
}

static void TXUdefineCurTex(char name[8], int16_t X, int16_t Y,
                            bool Redefn)
{
    int t;
    if (!TXUok)
        Bug("TX89", "TXUok");
    TXUtexCur = TXUtexTop;      /*set current entry */
    TXUtexTop += 1;             /*find a free position */
    if (TXUtexTop >= TXUtexMax) {
        TXUtexMax += NEWTEXTURES;
        TXUtex =
            (struct TEXTUR *) Realloc(TXUtex,
                                      TXUtexMax * sizeof(struct TEXTUR));
    }
    Normalise(TXUtex[TXUtexCur].Name, name);    /*declare texture */
    TXUtex[TXUtexCur].szX = X;
    TXUtex[TXUtexCur].szY = Y;
    TXUtex[TXUtexCur].Npatches = 0;
    /*check if we redefine other textures, and overide them. */
    for (t = 0; t < TXUtexCur; t++) {
        if (strncmp(TXUtex[t].Name, name, 8) == 0) {
            if (Redefn) {
                TXUtex[t].Name[0] = '\0';
                Detail("TX90", "Warning: texture %s is redefined",
                       lump_name(name));
            } else {
                /*don't redefine textures */
                TXUtex[TXUtexCur].Name[0] = '\0';
                break;
            }
        }
    }
}

static void TXUaddPatchToCurTex(int16_t pindex, int16_t ofsX, int16_t ofsY)
{
    char pname[8];
    if (!TXUok)
        Bug("TX91", "TXUok");
    if (TXUpatTop >= TXUpatMax) {
        TXUpatMax += NEWPATCHESDEF;
        TXUpat =
            (struct PATCH *) Realloc(TXUpat,
                                     TXUpatMax * sizeof(struct PATCH));
    }
    if (TXUtexCur < 0)
        Bug("TX92", "TXUTxC");
    PNMgetPatchName(pname, pindex);     /*check if index correct */
    TXUtex[TXUtexCur].Npatches += 1;    /*increase texture's patch counter */
    TXUpat[TXUpatTop].Pindex = pindex;  /*declare patch */
    TXUpat[TXUpatTop].ofsX = ofsX;
    TXUpat[TXUpatTop].ofsY = ofsY;
    TXUpatTop += 1;
}

void TXUfree(void)
{
    if (!TXUok)
        Bug("TX93", "TXUok");
    free(TXUpat);
    free(TXUtex);
    TXUok = false;
}

bool TXUexist(char *Name)
{
    int t;
    if (!TXUok)
        Bug("TX94", "TXUok");
    for (t = 0; t < TXUtexTop; t++) {
        if (strncmp(TXUtex[t].Name, Name, 8) == 0)
            return true;
    }
    return false;
}

/*
** find the number of real textures
*/
static int32_t TXUrealTexture(void)
{
    int16_t t;
    int32_t NbOfTex = 0;        /*real top of texs */
    for (t = 0; t < TXUtexTop; t++) {
        if (TXUtex[t].Npatches < 1) {
            Warning("TX10", "Ignored empty texture %s",
                    lump_name(TXUtex[t].Name));
            TXUtex[t].Name[0] = '\0';
        }
        if (TXUtex[t].Name[0] != '\0')
            NbOfTex++;
    }
    return NbOfTex;
}

int32_t TXUwriteTEXTUREtoWAD(struct WADINFO * info)
{
    int16_t t, tt, p, pat;
    int32_t size, ofsTble;
    int32_t NbOfTex;

    if (!TXUok)
        Bug("TX95", "TXUok");
    if (TXUtexTop < 1)
        Bug("TX96", "TxuNTx");
    /*count real textures */
    NbOfTex = TXUrealTexture();
    size = WADRwriteLong(info, NbOfTex);    /*number of entries */
    ofsTble = WADRposition(info);
    for (tt = 0; tt < NbOfTex; tt++) {
        size += WADRwriteLong(info, -1);    /*pointer, to be corrected later */
    }
    for (pat = 0, tt = 0, t = 0; t < TXUtexTop; t++) {
        if (TXUtex[t].Name[0] != '\0') {
            /*set texture direct pointer */
            if (tt >= NbOfTex)
                Bug("TX97", "TxuRT");
            WADRsetLong(info, ofsTble + tt * 4, size);
            tt++;
            /*write the begining of texture definition */
            fseek(info->fd, info->wposit, SEEK_SET);    /* Ugly */
            if (output_texture_format != TF_NAMELESS) {
                wad_write_name(info->fd, TXUtex[t].Name);
                size += 8;
            }
            wad_write_i16(info->fd, 0);
            size += 2;
            wad_write_i16(info->fd, 0);
            size += 2;
            wad_write_i16(info->fd, TXUtex[t].szX);
            size += 2;
            wad_write_i16(info->fd, TXUtex[t].szY);
            size += 2;
            if (output_texture_format != TF_STRIFE11) {
                wad_write_i16(info->fd, 0);
                size += 2;
                wad_write_i16(info->fd, 0);
                size += 2;
            }
            wad_write_i16(info->fd, TXUtex[t].Npatches);
            size += 2;
            for (p = 0; p < (TXUtex[t].Npatches); p++) {
                if (pat + p >= TXUpatTop)
                    /*number of patches exceeds definitions */
                    Bug("TX98", "TxuP>D");
                wad_write_i16(info->fd, TXUpat[pat + p].ofsX);
                size += 2;
                wad_write_i16(info->fd, TXUpat[pat + p].ofsY);
                size += 2;
                wad_write_i16(info->fd, TXUpat[pat + p].Pindex);
                size += 2;
                if (output_texture_format != TF_STRIFE11) {
                    wad_write_i16(info->fd, 0);
                    size += 2;
                    wad_write_i16(info->fd, 0);
                    size += 2;
                }
            }
            info->wposit = ftell(info->fd);     /* Ugly */
        }
        pat += TXUtex[t].Npatches;
    }
    return size;
}

/*
**
** convert raw data (as read from wad) into Textures
** Data= texture entry
** if PatchSz>0, Patch defines the patch list
*/
void TXUreadTEXTURE(const char *texture1_name, const char *Data,
                    int32_t DataSz, const char *Patch, int32_t PatchSz,
                    bool Redefn)
{
    int32_t Pos, Numtex, Numpat, dummy;
    /* texture data */
    int16_t t, p, i, Xsize, Ysize;      /* size x and y  */
    /* nb of patches used to build it */
    /* patch inside a texture */
    int16_t Xofs, Yofs, Pindex; /* x,y coordinate in texture space */
    /* patch name index in PNAMES table */
    int32_t MaxPindex;
    static char tname[10];       /*texture name */
    static char pname[8];       /*patch name */
    size_t header_size = 0;
    size_t item_size = 0;
    int have_texture_name = 0;
    int have_header_dummies = 0;

    if (input_texture_format == TF_NAMELESS) {
        header_size = 14;
        item_size = 10;
        have_texture_name = 0;
        have_header_dummies = 1;
    } else if (input_texture_format == TF_NONE) {
        Warning("TX11", "No texture definitions to read");
        return;                 /* FIXME is it OK to do that ? */
    } else if (input_texture_format == TF_NORMAL) {
        header_size = 22;
        item_size = 10;
        have_texture_name = 1;
        have_header_dummies = 1;
    } else if (input_texture_format == TF_STRIFE11) {
        header_size = 18;
        item_size = 6;
        have_texture_name = 1;
        have_header_dummies = 0;
    } else {
        Bug("TX99", "invalid itf %d", (int) input_texture_format);
        return;
    }

    /*get number of patches */
    if (PatchSz > 0) {
        MaxPindex = peek_i32_le(Patch);
        dummy = 4L + (MaxPindex * 8L);
        if (dummy > PatchSz)
            ProgError("PA03", "PNAMES is corrupt");
    } else {
        MaxPindex = PNMgetNbOfPatch();
    }
    if ((MaxPindex < 0) || (MaxPindex > 0x7FFF))
        ProgError("PA04", "PNAMES: invalid patch count %ld",
                  (long) MaxPindex);
    /*get number of textures */
    Numtex = peek_i32_le(Data);
    if (Numtex < 0)
        ProgError("TX12", "%s: invalid texture count %ld",
                  lump_name(texture1_name), (long) Numtex);
    if (Numtex > MAXTEXTURES)
        ProgError("TX13", "%s: too many textures (%ld/%ld)",
                  lump_name(texture1_name), (long) Numtex,
                  (long) MAXTEXTURES);

    /*read textures */
    for (t = 0; t < Numtex; t++) {
        Pos = peek_i32_le(Data + 4L + t * 4L);
        if (Pos + header_size > DataSz)
            ProgError("TX14", "%s: unexpected EOL in entry %d",
                      lump_name(texture1_name), (long) t);

        if (have_texture_name) {
            Normalise(tname, Data + Pos);
            Pos += 8;
        } else {                /* No name. Make one up (TEXnnnn) */

            if (t > 9999) {
                Warning("TX15",
                        "%s: more than 10000 textures, ignoring excess",
                        lump_name(texture1_name));
                break;
            }
            sprintf(tname, "TEX%04d", (int) t);
        }

        Pos += 4;               /* Skip 2 dummy unused fields */

        Xsize = peek_i16_le(Data + Pos);
        Pos += 2;
        if ((Xsize < 0) || (Xsize > 4096))
            ProgError("TX16", "%s: Texture %s: width out of bounds (%d)",
                      lump_name(texture1_name), lump_name(tname),
                      (int) Xsize);

        Ysize = peek_i16_le(Data + Pos);
        Pos += 2;
        if ((Ysize < 0) || (Ysize > 4096))
            ProgError("TX17", "%s: Texture %s: height out of bounds (%d)",
                      lump_name(texture1_name), lump_name(tname),
                      (int) Ysize);

        if (have_header_dummies)
            Pos += 4;           /* Skip 2 dummy unused fields */

        Numpat = peek_i16_le(Data + Pos) & 0xFFFF;
        Pos += 2;
        if (Numpat < 0) {
            nf_err("TX18",
                   "%s: Texture %s: negative patch count %d, skipping texture",
                   lump_name(texture1_name), lump_name(tname),
                   (int) Numpat);
            continue;
        }

        /* declare texture */
        TXUdefineCurTex(tname, Xsize, Ysize, Redefn);
        if (Pos + (long) Numpat * item_size > DataSz)
            ProgError("TX19", "%s(%ld): Texture %s: unexpected EOL",
                      lump_name(texture1_name), (long) t,
                      lump_name(tname));
        for (p = 0; p < Numpat; p++, Pos += item_size) {
            Xofs = peek_i16_le(Data + Pos);
            if ((Xofs < -4096) || (Xofs > 4096))
                ProgError("TX20",
                          "%s(%ld): Texture %s(%d/%d): bad patch X-offset %d",
                          lump_name(texture1_name), (long) t,
                          lump_name(tname), (int) p, (int) Numpat,
                          (int) Xofs);
            Yofs = peek_i16_le(Data + Pos + 2);
            if ((Yofs < -4096) || (Yofs > 4096))
                ProgError("TX21",
                          "%s(%ld): Texture %s(%d/%d): bad patch Y-offset %d",
                          lump_name(texture1_name), (long) t,
                          lump_name(tname), (int) p, (int) Numpat,
                          (int) Yofs);
            Pindex = peek_i16_le(Data + Pos + 4);
            if ((Pindex < 0) || (Pindex > MaxPindex))
                ProgError("TX22",
                          "%s(%ld): Texture %s(%d/%d): bad patch index %d",
                          lump_name(texture1_name), (long) t,
                          lump_name(tname), (int) p, (int) Numpat,
                          (int) Pindex);
            /*if new patch list, recalculate Pindex */
            if (PatchSz > 0) {
                for (dummy = (4L + (Pindex * 8L)), i = 0; i < 8; i++)
                    pname[i] = Patch[dummy + i];
                Pindex = PNMgetPatchIndex(pname);
            }
            /*declare patch */
            TXUaddPatchToCurTex(Pindex, Xofs, Yofs);
        }
    }
}

bool TXUcheckTex(int16_t npatch, int16_t * PszX)
{
    int16_t t, tt, p, pat, col, top, found;
    int16_t bit, C, b;          /*bit test */
    int16_t Meduza;
    bool Res = true;
    if (!TXUok)
        Bug("TX23", "TXUok");
    Output("Checking textures\n");
    if (TXUtexTop < 1)
        Bug("TX24", "TxuNTx");
    /* FIXME assign a code to this message */
    if (TXUtexTop < 100)
        Output
            ("Warning: Some textures could be missing! (less than 100 defined)\n");
    for (pat = 0, t = 0; t < TXUtexTop; t++) {
        if (TXUtex[t].Npatches < 1)
            /* FIXME assign a code to this message */
            {
                Output("Warning: Texture %s is empty\n",
                       lump_name(TXUtex[t].Name));
                Res = false;
            }
        top = pat + TXUtex[t].Npatches;
        if (top > TXUpatTop)
            Bug("TX25", "TxuP>D");
        /*
        ** check width
        */
        for (bit = 1, C = 0, b = 0; b < 16; b++, bit <<= 1)
            if ((TXUtex[t].szX) & bit)
                C++;
        if (C > 1) {
            /* FIXME assign a code to this message */
            Output("Warning: Width of %s is not a power of 2\n",
                   lump_name(TXUtex[t].Name));
            Res = false;
        }
        /*
        ** check height
        */
        if (TXUtex[t].szY > 128) {
            /* FIXME assign a code to this message */
            Output("Warning: Height of %s is more than 128\n",
                   lump_name(TXUtex[t].Name));
            Res = false;
        }
        /*
        ** check patch for:
        ** - void columns            (crashes the game at boot)
        ** - possible meduza effect  (if the patch is used on 2S walls)
        */
        Meduza = 0;
        for (col = 0; col < TXUtex[t].szX; col++) {
            if (Meduza < 2)
                Meduza = 0;     /*no Meduza effect found yet */
            found = false;
            for (p = 0; p < (TXUtex[t].Npatches); p++) {
                if (TXUpat[pat + p].Pindex >= npatch)
                    Bug("TX26", "~TxuP>D");
                if (col >= TXUpat[pat + p].ofsX) {
                    top =
                        PszX[TXUpat[pat + p].Pindex] + TXUpat[pat +
                                                              p].ofsX;
                    if (col < top) {
                        found = true;
                        if (Meduza >= 2)
                            break; // two patches on same column. Meduza effect
                        else
                            Meduza++;   /*keep looking for patches */
                    }
                }
            }
            if (!found) {
                /* FIXME assign a code to this message */
                Output("Warning: Empty column %d in texture %s\n", col,
                       lump_name(TXUtex[t].Name));
                Res = false;
            }
        }
        if (Meduza >= 2) {
            /*there is a column with two patches */
            /* FIXME assign a code to this message */
            Output
                ("Warning: Texture %s should not be used on a two sided wall.\n",
                 lump_name(TXUtex[t].Name));
        }
        pat += TXUtex[t].Npatches;
    }
    /*
    ** check duplication
    */
    for (t = 0; t < TXUtexTop; t++) {
        for (tt = t + 1; tt < TXUtexTop; tt++)
            if (strncmp(TXUtex[t].Name, TXUtex[tt].Name, 8) == 0) {
                /* FIXME assign a code to this message */
                Output("Warning: texture %s is duplicated\n",
                       lump_name(TXUtex[t].Name));
                Res = false;
            }
    }
    return Res;
}

/*
** this function shall only be used to add fake
** textures in order to list textures in levels
*/
void TXUfakeTex(char Name[8])
{
    if (!TXUok)
        Bug("TX27", "TXUok");
    /*if already exist */
    if (TXUexist(Name))
        return;
    TXUtexCur = TXUtexTop;      /*set current entry */
    TXUtexTop += 1;             /*find a free position */
    if (TXUtexTop >= TXUtexMax) {
        TXUtexMax += NEWTEXTURES;
        TXUtex =
            (struct TEXTUR *) Realloc(TXUtex,
                                      TXUtexMax * sizeof(struct TEXTUR));
    }
    Normalise(TXUtex[TXUtexCur].Name, Name);    /*declare texture */
    TXUtex[TXUtexCur].szX = 0;
    TXUtex[TXUtexCur].szY = 0;
    TXUtex[TXUtexCur].Npatches = 0;
}

/*
** list the names of the textures defined
*/
void TXUlistTex(void)
{
    int16_t t;
    if (!TXUok)
        Bug("TX28", "TXUok");
    for (t = 0; t < TXUtexTop; t++) {
        if (TXUtex[t].Name[0] != '\0')
            Output("%s\n", lump_name(TXUtex[t].Name));
    }
}

/*
** write texture as text file
*/
void TXUwriteTexFile(const char *file)
{
    int16_t t, p, pat, top;
    char pname[8];
    FILE *out;

    if (!TXUok)
        Bug("TX29", "TXUok");
    if (TXUtexTop < 1)
        Bug("TX30", "TxunTx");

    out = fopen(file, FOPEN_WT);
    if (out == NULL)
        ProgError("TX31", "%s: %s", file, strerror(errno));
    TXUrealTexture();
    fprintf(out, ";Format of textures:\n");
    fprintf(out, ";TextureName\tWidth\tHeight\n");
    fprintf(out, ";*\tPatchName\tXoffset\tYoffset\n");
    for (pat = 0, t = 0; t < TXUtexTop; t++) {
        if (TXUtex[t].Name[0] != '\0') {        /*if tex was not redefined */
            fprintf(out, "%-8.8s    ", TXUtex[t].Name);
            fprintf(out, "\t\t%d\t%d\n", TXUtex[t].szX, TXUtex[t].szY);
            for (p = 0; p < TXUtex[t].Npatches; p++) {
                top = pat + p;
                if (top >= TXUpatTop)
                    Bug("TX32", "TxuP>D");
                PNMgetPatchName(pname, TXUpat[pat + p].Pindex);
                fprintf(out, "*\t%-8.8s    ", pname);
                fprintf(out, "\t%d\t%d\n", TXUpat[pat + p].ofsX,
                        TXUpat[pat + p].ofsY);
            }
        }
        pat += TXUtex[t].Npatches;
    }
    fprintf(out, ";End\n");
    if (fclose(out) != 0)
        ProgError("TX33", "%s: %s", file, strerror(errno));
}

/*
** read texture as text file
**
*/
void TXUreadTexFile(const char *file, bool Redefn)
{
    int16_t Pindex;
    int16_t xsize = 0, ysize = 0, ofsx = 0, ofsy = 0;
    char tname[8];
    char pname[8];
    int16_t t, bit, C, b;       /*to check Xsize */
    struct TXTFILE *TXT;
    TXT = TXTopenR(file, 0);
    for (t = 0; t < MAXTEXTURES; t++) {
        if (!TXTreadTexDef(TXT, tname, &xsize, &ysize))
            break;
        /* check X size */
        if (xsize < 0)
            ProgError("TX35", "Texture %s: width %d < 0", lump_name(tname),
                      (int) xsize);
        if (xsize > 4096)
            Warning("TX36",
                    "Texture %s: width %d > 4096, XDoom 20001001 may crash",
                    lump_name(tname), (int) xsize);
        if (xsize > 512)
            Warning("TX37", "Texture %s: width %d > 512, Doom may freeze",
                    lump_name(tname), (int) xsize);
        for (bit = 1, C = 0, b = 0; b < 16; b++, bit <<= 1)
            if (xsize & bit)
                C++;
        if (C > 1)
            Warning("TX38", "Texture %s: width %d not a power of 2",
                    lump_name(tname), (int) xsize);
        /* check Y size */
        if (ysize < 0)
            ProgError("TX39", "Texture %s: height %d < 0",
                      lump_name(tname), (int) ysize);
        if (ysize > 128)
            Warning("TX40",
                    "Texture %s: height %d > 128, won't be rendered properly",
                    lump_name(tname), (int) ysize);
        /* declare texture */
        TXUdefineCurTex(tname, xsize, ysize, Redefn);
        {
            long npat = 0;

            while (TXTreadPatchDef(TXT, pname, &ofsx, &ofsy)) {
                if (npat == MAX_PATCHES)
                    Warning("TX42",
                            "Texture %s: more than 32767 patches, ignoring excess",
                            lump_name(tname), ofsx);
                else if (npat > MAX_PATCHES);   /* Silently drop the rest */
                else {
                    /* declare a patch wall, getting index or
                       declaring a new index */
                    Pindex = PNMgetPatchIndex(pname);
                    TXUaddPatchToCurTex(Pindex, ofsx, ofsy);
                }
                if (npat <= MAX_PATCHES)
                    npat++;
            }
        }
    }
    Phase("TX44", "Read %d textures from %s", t, file);
    TXTcloseR(TXT);
}
