#pragma once

namespace nall { namespace mosaic {

struct parser {
  //export from bitstream to canvas
  auto load(bitstream& stream, uint64_t offset, context& ctx, uint width, uint height) -> void {
    canvas.allocate(width, height);
    canvas.fill(ctx.paddingColor);
    parse(1, stream, offset, ctx, width, height);
  }

  //import from canvas to bitstream
  auto save(bitstream& stream, uint64_t offset, context& ctx) -> bool {
    if(stream.readonly) return false;
    parse(0, stream, offset, ctx, canvas.width(), canvas.height());
    return true;
  }

private:
  auto read(uint x, uint y) const -> uint32_t {
    uint addr = y * canvas.width() + x;
    if(addr >= canvas.width() * canvas.height()) return 0u;
    auto buffer = (uint32_t*)canvas.data();
    return buffer[addr];
  }

  auto write(uint x, uint y, uint32_t data) -> void {
    uint addr = y * canvas.width() + x;
    if(addr >= canvas.width() * canvas.height()) return;
    auto buffer = (uint32_t*)canvas.data();
    buffer[addr] = data;
  }

  auto parse(bool load, bitstream& stream, uint64_t offset, context& ctx, uint width, uint height) -> void {
    stream.endian = ctx.endian;
    uint canvasWidth = width / (ctx.mosaicWidth * ctx.tileWidth * ctx.blockWidth + ctx.paddingWidth);
    uint canvasHeight = height / (ctx.mosaicHeight * ctx.tileHeight * ctx.blockHeight + ctx.paddingHeight);
    uint bitsPerBlock = ctx.depth * ctx.blockWidth * ctx.blockHeight;

    uint objectOffset = 0;
    for(uint objectY = 0; objectY < canvasHeight; objectY++) {
      for(uint objectX = 0; objectX < canvasWidth; objectX++) {
        if(objectOffset >= ctx.count && ctx.count > 0) break;
        uint objectIX = objectX * ctx.objectWidth();
        uint objectIY = objectY * ctx.objectHeight();
        objectOffset++;

        uint mosaicOffset = 0;
        for(uint mosaicY = 0; mosaicY < ctx.mosaicHeight; mosaicY++) {
          for(uint mosaicX = 0; mosaicX < ctx.mosaicWidth; mosaicX++) {
            uint mosaicData = ctx.mosaic(mosaicOffset, mosaicOffset);
            uint mosaicIX = (mosaicData % ctx.mosaicWidth) * (ctx.tileWidth * ctx.blockWidth);
            uint mosaicIY = (mosaicData / ctx.mosaicWidth) * (ctx.tileHeight * ctx.blockHeight);
            mosaicOffset++;

            uint tileOffset = 0;
            for(uint tileY = 0; tileY < ctx.tileHeight; tileY++) {
              for(uint tileX = 0; tileX < ctx.tileWidth; tileX++) {
                uint tileData = ctx.tile(tileOffset, tileOffset);
                uint tileIX = (tileData % ctx.tileWidth) * ctx.blockWidth;
                uint tileIY = (tileData / ctx.tileWidth) * ctx.blockHeight;
                tileOffset++;

                uint blockOffset = 0;
                for(uint blockY = 0; blockY < ctx.blockHeight; blockY++) {
                  for(uint blockX = 0; blockX < ctx.blockWidth; blockX++) {
                    if(load) {
                      uint palette = 0;
                      for(uint n = 0; n < ctx.depth; n++) {
                        uint index = blockOffset++;
                        if(ctx.order == 1) index = (index % ctx.depth) * ctx.blockWidth * ctx.blockHeight + (index / ctx.depth);
                        palette |= stream.read(offset + ctx.block(index, index)) << n;
                      }

                      write(
                        objectIX + mosaicIX + tileIX + blockX,
                        objectIY + mosaicIY + tileIY + blockY,
                        ctx.palette(palette, palette)
                      );
                    } else /* save */ {
                      uint32_t palette = read(
                        objectIX + mosaicIX + tileIX + blockX,
                        objectIY + mosaicIY + tileIY + blockY
                      );

                      for(uint n = 0; n < ctx.depth; n++) {
                        uint index = blockOffset++;
                        if(ctx.order == 1) index = (index % ctx.depth) * ctx.blockWidth * ctx.blockHeight + (index / ctx.depth);
                        stream.write(offset + ctx.block(index, index), palette & 1);
                        palette >>= 1;
                      }
                    }
                  }  //blockX
                }  //blockY

                offset += ctx.blockStride;
              }  //tileX

              offset += ctx.blockOffset;
            }  //tileY

            offset += ctx.tileStride;
          }  //mosaicX

          offset += ctx.tileOffset;
        }  //mosaicY

        offset += ctx.mosaicStride;
      }  //objectX

      offset += ctx.mosaicOffset;
    }  //objectY
  }

  image canvas;
};

}}
