<template>
  <div class="collage">
    <div class="canvas">
      <canvas
        id="canvas"
        ref="canvas"
        :width="canvasSize.width"
        :height="canvasSize.height"
      ></canvas>
      <div class="canvas-size">
        <label>
          Width:
          <input type="number" step="16" min="128" v-model="canvasSize.width" />
        </label>
        <label>
          Height:
          <input
            type="number"
            step="16"
            min="128"
            v-model="canvasSize.height"
          />
        </label>
      </div>
    </div>
    <div class="controls">
      <div class="modes">
        <ul class="modes-list">
          <li
            v-for="(mode, idx) in modes.modes"
            :class="[
              'mode',
              {
                disabled: images.length < mode.minImages,
                excluded: excludedModes.includes(idx),
                selected: idx === currentModeType,
                lastActive: lastActiveModeTypes.includes(idx),
              },
            ]"
            :key="idx"
          >
            <label class="handle"
              >{{ mode.name
              }}<input type="radio" :value="idx" v-model="currentModeType"
            /></label>
            <span
              class="mode-plus"
              v-if="
                ['recursive', 'shuffle'].includes(currentModeType) &&
                images.length >= minImages
              "
            >
              <span class="separator"></span>
              <label
                >{{ excludedModes.includes(idx) ? "o" : "x"
                }}<input type="checkbox" :value="idx" v-model="excludedModes"
              /></label>
            </span>
          </li>
        </ul>
        <hr />
        <label
          :class="{
            disabled: images.length < minImages,
            selected: 'shuffle' === currentModeType,
          }"
        >
          !SHUFFLE ALL!
          <input type="radio" value="shuffle" v-model="currentModeType" />
        </label>
        <label
          :class="{
            disabled: images.length < minImages,
            selected: 'recursive' === currentModeType,
          }"
        >
          #RECURSIVE#
          <input type="radio" value="recursive" v-model="currentModeType" />
        </label>
      </div>
      <button :disabled="images.length < minImages" @click="renderCollage">
        REPAINT
      </button>
      <hr />
      <div class="config">
        <template v-if="currentModeType !== 'recursive'">
          <label class="config-numimages">
            #N of images:
            <input
              type="number"
              :min="minImages"
              :max="images.length"
              placeholder="RND"
              :disabled="Object.keys(forceConfig).includes('numImages')"
              :value="forceConfig.numImages || collageConfig.numImages"
              @input="(n) => (collageConfig.numImages = n)"
            />
          </label>
        </template>
        <template v-else>
          <label>
            Recursion levels:
            <input
              type="number"
              :min="1"
              :max="10"
              v-model="recursiveConfig.level"
            />
          </label>
          <label>
            <input type="checkbox" v-model="recursiveConfig.repeat" />
            Repeat images?
          </label>
        </template>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import BrowserCollageModes from "../collages";
import { CollageModeType } from "../common/collages";
import { CollageConfig, CollageMode, Segment } from "../common/types";
import { choice, shuffle } from "../common/utils";

type DisplayCollageModeType = CollageModeType | "shuffle" | "recursive";

@Component
export default class Collage extends Vue {
  @Prop({ required: true }) private images!: ImageBitmap[];
  private context!: CanvasRenderingContext2D;
  private canvasSize = {
    width: 640,
    height: 640,
  };
  private collageConfig: CollageConfig = {
    numImages: undefined,
  };
  private recursiveConfig = {
    level: 2,
    repeat: true,
  };
  private currentModeType: DisplayCollageModeType = "shuffle";
  private lastActiveModeTypes: CollageModeType[] = [];
  private excludedModes: CollageModeType[] = [];
  private modes = new BrowserCollageModes();

  private get minImages() {
    if (
      this.currentModeType === "shuffle" ||
      this.currentModeType === "recursive"
    ) {
      return Math.min(
        ...Object.values(this.modes.modes).map((mode) => mode.minImages)
      );
    } else {
      return this.modes.modes[this.currentModeType].minImages;
    }
  }

  private get lastMode() {
    if (this.lastActiveModeTypes.length === 1) {
      return this.modes.modes[this.lastActiveModeTypes[0]];
    }
  }

  private get forceConfig() {
    return this.lastMode ? this.lastMode.forceConfig || {} : {};
  }

  private mounted() {
    const canvas = this.$refs.canvas as HTMLCanvasElement;
    this.context = canvas.getContext("2d") as CanvasRenderingContext2D;
  }

  @Watch("images")
  @Watch("currentModeType")
  @Watch("collageConfig", { deep: true })
  @Watch("recursiveConfig", { deep: true })
  private renderCollage() {
    if (this.images.length >= this.minImages) {
      this.reset();

      const permissibleModeKeys = (
        Object.keys(this.modes.modes) as CollageModeType[]
      ).filter(
        (k) =>
          !this.excludedModes.includes(k) &&
          this.modes.modes[k].minImages <= this.images.length
      );

      if (this.currentModeType !== "recursive") {
        let mode: CollageMode<any, any>;
        if (this.currentModeType === "shuffle") {
          const randomModeType = choice(permissibleModeKeys);
          this.lastActiveModeTypes = [randomModeType];
          mode = this.modes.modes[randomModeType];
        } else {
          this.lastActiveModeTypes = [this.currentModeType];
          mode = this.modes.modes[this.currentModeType];
        }
        const shuffledImages = shuffle(this.images);
        const segments = mode.getSegments(
          this.context,
          this.collageConfig,
          shuffledImages
        );
        mode.place(this.context, shuffledImages, segments);
      } else {
        this.lastActiveModeTypes = [];
        const shuffledImages = shuffle(this.images);
        const rootSegment: Segment = {
          x: 0,
          y: 0,
          w: this.context.canvas.width,
          h: this.context.canvas.height,
        };
        const processSegment = async (
          segment: Segment,
          level: number
        ): Promise<ImageBitmap> => {
          console.debug(segment, level);
          if (
            segment === rootSegment ||
            level <= this.recursiveConfig.level - 1
          ) {
            let canvas = document.createElement("canvas");
            canvas.width = segment.w;
            canvas.height = segment.h;
            let modeKey = choice(permissibleModeKeys);
            console.debug(modeKey);
            this.lastActiveModeTypes.push(modeKey);
            let mode = this.modes.modes[modeKey];
            let ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
            let segments = mode.getSegments(ctx);
            console.debug(segments);
            let bitmaps = await Promise.all(
              segments.map((segment) => processSegment(segment, level + 1))
            );
            mode.place(ctx, bitmaps, segments);
            return await createImageBitmap(canvas);
          } else {
            if (this.recursiveConfig.repeat) {
              return choice(shuffledImages);
            } else {
              if (shuffledImages.length > 0) {
                return shuffledImages.pop() as ImageBitmap;
              } else {
                throw "RAN OUT OF IMAGES";
              }
            }
          }
        };
        processSegment(rootSegment, 0)
          .then((finalCollage) => {
            console.debug(finalCollage);
            this.context.drawImage(finalCollage, 0, 0);
          })
          .catch((error) => {
            alert(error);
          });
      }
    }
  }

  @Watch("canvasSize", { deep: true })
  private onCanvasSizeChange() {
    this.$nextTick(() => {
      this.renderCollage();
    });
  }

  private reset() {
    this.context.globalCompositeOperation = "source-over";
    const canvas = this.$refs.canvas as HTMLCanvasElement;
    this.context.clearRect(0, 0, canvas.width, canvas.height);
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<!--suppress CssUnusedSymbol -->
<style scoped>
.collage {
  margin: 2rem;
  display: flex;
  justify-content: space-evenly;
  align-items: center;
}

.canvas {
  display: flex;
  flex-direction: column;
  align-items: center;
}

#canvas {
  border: 1px solid black;
}

.canvas-size {
  margin: 1rem;
}

.controls button {
  width: 100%;
}

.modes {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.modes-list {
  list-style: none;
  padding: 0;
}

.mode {
  display: flex;
  justify-content: center;
  align-items: center;
  user-select: none;
}

.separator {
  border: 1px solid gray;
  width: 0;
  margin: 0 0.5em;
  align-self: stretch;
}

.mode .mode-plus,
.mode .mode-plus label {
  color: gray;
  font-size: 14px;
}

.mode-plus {
  display: flex;
  align-items: center;
}

.controls .modes hr {
  margin-top: 0.5rem;
  width: 100%;
  color: lightgray;
}

.controls label {
  font-size: 14pt;
  cursor: pointer;
  margin: 0.25rem;
}

.modes input {
  display: none;
}

.controls button,
.controls hr,
.controls .config {
  margin-top: 1rem;
}

.controls .config {
  user-select: none;
}

.config label {
  display: block;
}

.config-numimages input {
  width: 4em;
}

.disabled {
  color: gray;
  pointer-events: none;
}

.excluded .handle {
  color: gray;
  text-decoration: line-through;
}

.selected {
  font-weight: bold;
}

.lastActive .handle {
  text-decoration: underline;
}
</style>
