<template>
  <div class="terrain-tool-container">
    <div class="header">
      <h1>地形纹理合成工具</h1>
      <p class="subtitle">将颜色、法线和粗糙度贴图合成为地形使用的TGA格式</p>
    </div>

    <div class="upload-section">
      <div class="upload-card" :class="{ active: colorFiles.length > 0 }">
        <label class="upload-label">
          <input type="file" multiple accept="image/*" @change="onFilesSelected('color', $event)" />
          <div class="upload-content">
            <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
              <circle cx="8.5" cy="8.5" r="1.5"></circle>
              <polyline points="21 15 16 10 5 21"></polyline>
            </svg>
            <h3>颜色贴图</h3>
            <p v-if="colorFiles.length === 0">点击或拖放文件到这里</p>
            <p v-else>{{ colorFiles.length }} 个文件已选择</p>
          </div>
        </label>
      </div>

      <div class="upload-card" :class="{ active: normalFiles.length > 0 }">
        <label class="upload-label">
          <input type="file" multiple accept="image/*" @change="onFilesSelected('normal', $event)" />
          <div class="upload-content">
            <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"></path>
            </svg>
            <h3>法线贴图(OpenGL格式)</h3>
            <p v-if="normalFiles.length === 0">点击或拖放文件到这里</p>
            <p v-else>{{ normalFiles.length }} 个文件已选择</p>
          </div>
        </label>
      </div>

      <div class="upload-card" :class="{ active: roughnessFiles.length > 0 }">
        <label class="upload-label">
          <input type="file" multiple accept="image/*" @change="onFilesSelected('roughness', $event)" />
          <div class="upload-content">
            <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="12" cy="12" r="10"></circle>
              <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
              <line x1="9" y1="9" x2="9.01" y2="9"></line>
              <line x1="15" y1="9" x2="15.01" y2="9"></line>
            </svg>
            <h3>粗糙度贴图</h3>
            <p v-if="roughnessFiles.length === 0">点击或拖放文件到这里</p>
            <p v-else>{{ roughnessFiles.length }} 个文件已选择</p>
          </div>
        </label>
      </div>
    </div>

    <div class="status-section">
      <div class="status-card">
        <div class="status-item">
          <span class="status-label">颜色贴图:</span>
          <span class="status-value">{{ colorFiles.length }}</span>
        </div>
        <div class="status-item">
          <span class="status-label">法线贴图:</span>
          <span class="status-value">{{ normalFiles.length }}</span>
        </div>
        <div class="status-item">
          <span class="status-label">粗糙度贴图:</span>
          <span class="status-value">{{ roughnessFiles.length }}</span>
        </div>
        <div class="status-item highlight">
          <span class="status-label">可匹配文件组:</span>
          <span class="status-value">{{ getMatchCount() }}</span>
        </div>
      </div>
    </div>

    <div class="action-buttons">
      <button 
        class="btn generate-btn" 
        :class="{ disabled: !canProcess }" 
        @click="generateTGA"
        :disabled="!canProcess">
        <span v-if="!isProcessing">生成地形TGA文件</span>
        <span v-else class="processing">
          <svg class="spinner" viewBox="0 0 50 50">
            <circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
          </svg>
          处理中...
        </span>
      </button>

      <button 
        class="btn download-btn" 
        :class="{ disabled: generatedFiles.length === 0 }" 
        @click="downloadZip"
        :disabled="generatedFiles.length === 0">
        打包下载ZIP
      </button>
    </div>

    <div class="results-section" v-if="generatedFiles.length > 0">
      <h2>生成结果</h2>
      <div class="results-grid">
        <div v-for="(file, index) in generatedFiles" :key="index" class="result-item">
          <div class="file-icon">
            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
              <polyline points="13 2 13 9 20 9"></polyline>
            </svg>
          </div>
          <div class="file-name">{{ file.name }}</div>
          <div class="file-size">{{ formatFileSize(file.blob.size) }}</div>
        </div>
      </div>
    </div>

    <div class="instructions" v-if="generatedFiles.length === 0 && !canProcess">
      <h3>使用说明</h3>
      <ol>
        <li>上传颜色、法线和粗糙度贴图（PNG/JPG格式）</li>
        <li>确保每组贴图有相同的文件名（仅扩展名不同）</li>
        <li>点击"生成地形TGA文件"按钮</li>
        <li>处理完成后下载ZIP包</li>
      </ol>
      <p class="note">注意：系统会自动匹配文件名相同的贴图进行合成处理。</p>
    </div>
  </div>
</template>

<script>
import JSZip from "jszip";

export default {
  name: "TerrainTool",
  data() {
    return {
      colorFiles: [],
      normalFiles: [],
      roughnessFiles: [],
      generatedFiles: [],
      isProcessing: false,
    };
  },
  computed: {
    canProcess() {
      return (
        this.colorFiles.length > 0 &&
        this.normalFiles.length > 0 &&
        this.roughnessFiles.length > 0 &&
        this.getMatchCount() > 0
      );
    },
  },
  methods: {
    onFilesSelected(type, e) {
      const files = Array.from(e.target.files);
      this[type + "Files"] = files;
    },

    getMatchCount() {
      const names = (list) => list.map((f) => f.name.split('.')[0]);
      const common = names(this.colorFiles).filter(
        (n) => names(this.normalFiles).includes(n) && names(this.roughnessFiles).includes(n)
      );
      return common.length;
    },

    async generateTGA() {
      if (!this.canProcess || this.isProcessing) return;
      
      this.isProcessing = true;
      this.generatedFiles = [];
      
      try {
        const nameMap = (files) => Object.fromEntries(files.map((f) => [f.name.split('.')[0], f]));
        const colorMap = nameMap(this.colorFiles);
        const normalMap = nameMap(this.normalFiles);
        const roughMap = nameMap(this.roughnessFiles);

        const commonKeys = Object.keys(colorMap).filter(
          (k) => normalMap[k] && roughMap[k]
        );

        for (const key of commonKeys) {
          const color = await this.loadImage(colorMap[key]);
          const normal = await this.loadImage(normalMap[key]);
          const rough = await this.loadImage(roughMap[key]);

          const [resultColor, resultNRM] = this.processImages(color, normal, rough);
          this.generatedFiles.push({ name: `${key}_ResultColor.tga`, blob: resultColor });
          this.generatedFiles.push({ name: `${key}_ResultNRM.tga`, blob: resultNRM });
        }
      } catch (error) {
        console.error("生成过程中出错:", error);
        alert("处理过程中发生错误，请检查文件格式后再试。");
      } finally {
        this.isProcessing = false;
      }
    },

    loadImage(file) {
      return new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = reject;
        img.src = URL.createObjectURL(file);
      });
    },

    processImages(colorImg, normalImg, roughImg) {
      const canvas = document.createElement("canvas");
      canvas.width = 256;
      canvas.height = 256;
      const ctx = canvas.getContext("2d");

      // COLOR+ROUGHNESS
      ctx.drawImage(colorImg, 0, 0, 256, 256);
      const rgbData = ctx.getImageData(0, 0, 256, 256);

      ctx.clearRect(0, 0, 256, 256);
      ctx.drawImage(roughImg, 0, 0, 256, 256);
      const roughData = ctx.getImageData(0, 0, 256, 256);

      const rgbaData = ctx.createImageData(256, 256);
      for (let i = 0; i < rgbaData.data.length; i += 4) {
        rgbaData.data[i] = rgbData.data[i];     // R
        rgbaData.data[i + 1] = rgbData.data[i + 1]; // G
        rgbaData.data[i + 2] = rgbData.data[i + 2]; // B
        rgbaData.data[i + 3] = 255 - roughData.data[i]; // A = 反相粗糙度
      }
      ctx.putImageData(rgbaData, 0, 0);
      const resultColorBlob = this.toTGA(ctx, 256, 256);

      // NORMAL
      ctx.clearRect(0, 0, 256, 256);
      ctx.drawImage(normalImg, 0, 0, 256, 256);
      const resultNRMBlob = this.toTGA(ctx, 256, 256);

      return [resultColorBlob, resultNRMBlob];
    },

    toTGA(ctx, width, height) {
      const pixels = ctx.getImageData(0, 0, width, height).data;
      const header = new Uint8Array(18);
      header[2] = 2;
      header[12] = width & 0xff;
      header[13] = (width >> 8) & 0xff;
      header[14] = height & 0xff;
      header[15] = (height >> 8) & 0xff;
      header[16] = 32;
      header[17] = 0x00; // 原点左下

      const data = new Uint8Array(width * height * 4);
      let offset = 0;
      for (let y = height - 1; y >= 0; y--) {
        for (let x = 0; x < width; x++) {
          const i = (y * width + x) * 4;
          data[offset++] = pixels[i + 2]; // B
          data[offset++] = pixels[i + 1]; // G
          data[offset++] = pixels[i];     // R
          data[offset++] = pixels[i + 3]; // A
        }
      }

      return new Blob([header, data], { type: "application/octet-stream" });
    },

    async downloadZip() {
      if (this.generatedFiles.length === 0) return;
      
      try {
        const zip = new JSZip();
        for (const file of this.generatedFiles) {
          zip.file(file.name, file.blob);
        }
        const blob = await zip.generateAsync({ type: "blob" });
        const a = document.createElement("a");
        a.href = URL.createObjectURL(blob);
        a.download = "TerrainTextures.zip";
        a.click();
      } catch (error) {
        console.error("打包下载出错:", error);
        alert("创建ZIP文件时出错，请重试。");
      }
    },

    formatFileSize(bytes) {
      if (bytes === 0) return '0 Bytes';
      const k = 1024;
      const sizes = ['Bytes', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }
  },
};
</script>

<style scoped>
.terrain-tool-container {
  max-width: 1000px;
  margin: 0 auto;
  padding: 2rem;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  color: #333;
}

.header {
  text-align: center;
  margin-bottom: 2rem;
}

.header h1 {
  font-size: 2.2rem;
  color: #2c3e50;
  margin-bottom: 0.5rem;
}

.subtitle {
  color: #7f8c8d;
  font-size: 1.1rem;
}

.upload-section {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
  margin-bottom: 2rem;
}

.upload-card {
  border: 2px dashed #bdc3c7;
  border-radius: 12px;
  padding: 1.5rem;
  transition: all 0.3s ease;
  background-color: #f8f9fa;
}

.upload-card.active {
  border-color: #3498db;
  background-color: #e8f4fc;
}

.upload-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}

.upload-label {
  display: block;
  cursor: pointer;
}

.upload-label input {
  display: none;
}

.upload-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.upload-content svg {
  margin-bottom: 1rem;
  color: #7f8c8d;
}

.upload-card.active svg {
  color: #3498db;
}

.upload-content h3 {
  margin: 0 0 0.5rem;
  color: #2c3e50;
}

.upload-content p {
  margin: 0;
  color: #7f8c8d;
  font-size: 0.9rem;
}

.status-section {
  margin-bottom: 2rem;
}

.status-card {
  background: #fff;
  border-radius: 10px;
  padding: 1.5rem;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1rem;
}

.status-item {
  display: flex;
  justify-content: space-between;
  padding: 0.5rem 0;
  border-bottom: 1px solid #eee;
}

.status-item.highlight {
  font-weight: bold;
  color: #3498db;
  border-bottom: none;
}

.status-label {
  color: #7f8c8d;
}

.status-value {
  font-weight: bold;
}

.action-buttons {
  display: flex;
  justify-content: center;
  gap: 1rem;
  margin-bottom: 2rem;
  flex-wrap: wrap;
}

.btn {
  padding: 0.8rem 1.8rem;
  border: none;
  border-radius: 8px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s ease;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.btn:disabled {
  cursor: not-allowed;
  opacity: 0.7;
}

.generate-btn {
  background-color: #3498db;
  color: white;
  min-width: 200px;
}

.generate-btn:hover:not(:disabled) {
  background-color: #2980b9;
  transform: translateY(-2px);
  box-shadow: 0 4px 8px rgba(52, 152, 219, 0.3);
}

.download-btn {
  background-color: #2ecc71;
  color: white;
}

.download-btn:hover:not(:disabled) {
  background-color: #27ae60;
  transform: translateY(-2px);
  box-shadow: 0 4px 8px rgba(46, 204, 113, 0.3);
}

.btn.disabled {
  background-color: #bdc3c7;
}

.processing {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.spinner {
  width: 20px;
  height: 20px;
  animation: rotate 2s linear infinite;
}

.spinner .path {
  stroke: white;
  stroke-linecap: round;
  animation: dash 1.5s ease-in-out infinite;
}

@keyframes rotate {
  100% {
    transform: rotate(360deg);
  }
}

@keyframes dash {
  0% {
    stroke-dasharray: 1, 150;
    stroke-dashoffset: 0;
  }
  50% {
    stroke-dasharray: 90, 150;
    stroke-dashoffset: -35;
  }
  100% {
    stroke-dasharray: 90, 150;
    stroke-dashoffset: -124;
  }
}

.results-section {
  margin-top: 2rem;
  animation: fadeIn 0.5s ease;
}

.results-section h2 {
  color: #2c3e50;
  margin-bottom: 1rem;
  padding-bottom: 0.5rem;
  border-bottom: 2px solid #eee;
}

.results-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1rem;
}

.result-item {
  background: #fff;
  border-radius: 8px;
  padding: 1rem;
  display: flex;
  align-items: center;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  transition: transform 0.2s ease;
}

.result-item:hover {
  transform: translateY(-3px);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.file-icon {
  margin-right: 1rem;
  color: #3498db;
}

.file-name {
  flex: 1;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-size {
  color: #7f8c8d;
  font-size: 0.85rem;
}

.instructions {
  background: #f8f9fa;
  border-radius: 10px;
  padding: 1.5rem;
  margin-top: 2rem;
  border-left: 4px solid #3498db;
}

.instructions h3 {
  color: #2c3e50;
  margin-top: 0;
}

.instructions ol {
  padding-left: 1.5rem;
  line-height: 1.6;
}

.instructions .note {
  font-style: italic;
  color: #7f8c8d;
  margin-top: 1rem;
  font-size: 0.9rem;
}

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (max-width: 768px) {
  .upload-section {
    grid-template-columns: 1fr;
  }
  
  .action-buttons {
    flex-direction: column;
  }
  
  .btn {
    width: 100%;
  }
}
</style>