// Copyright 2014 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ingore

package main

import (
	"bufio"
	"bytes"
	"fmt"
	"go/format"
	"io/ioutil"
	"log"
	"strconv"
	"strings"
	"text/scanner"
)

type Type struct {
	TypeName       string
	FileName       string
	FileData       []byte
	TypeList       []string
	TypeCommentMap map[string]string
	TagTypeMap     map[string][]string
	TagNumMap      map[string][]int
	TagIFDMap      map[string]string
	TagIngoreMap   map[string]bool
	MapCode        string
}

func main() {
	var types = []Type{
		Type{
			TypeName: "TiffType",
			FileName: "tiff_types.go",
		},
		Type{
			TypeName: "ImageType",
			FileName: "tiff_types.go",
		},
		Type{
			TypeName: "DataType",
			FileName: "tiff_types.go",
		},
		Type{
			TypeName: "TagType",
			FileName: "tiff_types.go",
		},

		Type{
			TypeName: "TagValue_NewSubfileType",
			FileName: "tiff_types.go",
		},
		Type{
			TypeName: "TagValue_SubfileType",
			FileName: "tiff_types.go",
		},
		Type{
			TypeName: "TagValue_CompressionType",
			FileName: "tiff_types.go",
		},
		Type{
			TypeName: "TagValue_PhotometricType",
			FileName: "tiff_types.go",
		},
		Type{
			TypeName: "TagValue_PredictorType",
			FileName: "tiff_types.go",
		},
		Type{
			TypeName: "TagValue_ResolutionUnitType",
			FileName: "tiff_types.go",
		},
		Type{
			TypeName: "TagValue_SampleFormatType",
			FileName: "tiff_types.go",
		},

		Type{
			TypeName: "ExifIFD_TagType",
			FileName: "tiff_types.go",
		},
		Type{
			TypeName: "GPSIFD_TagType",
			FileName: "tiff_types.go",
		},
		Type{
			TypeName: "InteroperabilityIFD_TagType",
			FileName: "tiff_types.go",
		},
	}

	var buf bytes.Buffer
	fmt.Fprintf(&buf, `
// Copyright 2014 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Auto generated by gen_helper.go, DO NOT EDIT!!!

package tiff

import (
	"fmt"
	"time"
)

`[1:])

	for _, v := range types {
		v.Init()
		v.GenMapCode()

		fmt.Fprintf(&buf, "%s\n", v.MapCode)
	}

	data, err := format.Source(buf.Bytes())
	if err != nil {
		log.Fatal(err)
	}
	err = ioutil.WriteFile("z_tiff_types_string.go", data, 0644)
	if err != nil {
		log.Fatal(err)
	}
}

func (p *Type) Init() {
	if p.TypeCommentMap == nil {
		p.TypeCommentMap = make(map[string]string)
	}
	if p.TagTypeMap == nil {
		p.TagTypeMap = make(map[string][]string)
	}
	if p.TagNumMap == nil {
		p.TagNumMap = make(map[string][]int)
	}
	if p.TagIFDMap == nil {
		p.TagIFDMap = make(map[string]string)
	}
	if p.TagIngoreMap == nil {
		p.TagIngoreMap = make(map[string]bool)
	}

	data, err := ioutil.ReadFile(p.FileName)
	if err != nil {
		log.Fatal(err)
	}
	p.FileData = data

	var s scanner.Scanner
	s.Init(bytes.NewReader(p.FileData))

	for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
		if tok&scanner.ScanIdents != 0 {
			if strings.HasPrefix(s.TokenText(), p.TypeName+"_") {
				typeName := s.TokenText()
				if _, ok := p.TypeCommentMap[typeName]; !ok {
					comment, _, _ := bufio.NewReader(bytes.NewReader(p.FileData[s.Pos().Offset:])).ReadLine()
					if idx := bytes.Index(comment, []byte("//")); idx >= 0 {
						comment = comment[idx:]
					} else {
						comment = nil
					}
					p.TypeList = append(p.TypeList, typeName)
					p.TypeCommentMap[typeName] = string(comment)
				}
			}
		}
	}
	if p.TypeName == "TagType" {
		p.parseTagComment()
	}
}

func (p *Type) parseTagComment() {
	if p.TypeName != "TagType" {
		return
	}
	// Type(A/B/C/*), Num(1/*), Required, # comment
	for typeName, comment := range p.TypeCommentMap {
		if strings.HasPrefix(comment, "//") {
			comment = strings.TrimSpace(comment[len("//"):])
		}
		if idx := strings.Index(comment, "#"); idx >= 0 {
			comment = comment[:idx]
		}
		comment = strings.TrimSpace(comment)

		if strings.Contains(comment, "TagType_ExifIFD") {
			p.TagIFDMap[typeName] = "TagType_ExifIFD"
		}
		if strings.Contains(comment, "TagType_GPSIFD") {
			p.TagIFDMap[typeName] = "TagType_GPSIFD"
		}
		if strings.Contains(comment, "TagType_InteroperabilityIFD") {
			p.TagIFDMap[typeName] = "TagType_InteroperabilityIFD"
		}

		if strings.Contains(comment, "ingore") {
			p.TagIngoreMap[typeName] = true
			continue
		}
		if comment == "" {
			continue
		}

		ss := strings.Split(comment, ",")
		if len(ss) > 0 {
			var types []string
			for _, dataType := range strings.Split(strings.TrimSpace(ss[0]), "/") {
				switch dataType {
				case "BYTE":
					types = append(types, "DataType_Byte")
				case "ASCII":
					types = append(types, "DataType_ASCII")
				case "SHORT":
					types = append(types, "DataType_Short")
				case "LONG":
					types = append(types, "DataType_Long")
				case "RATIONAL":
					types = append(types, "DataType_Rational")
				case "SBYTE":
					types = append(types, "DataType_SByte")
				case "UNDEFINED":
					types = append(types, "DataType_Undefined")
				case "SSHORT":
					types = append(types, "DataType_SShort")
				case "SLONG":
					types = append(types, "DataType_SLong")
				case "SRATIONAL":
					types = append(types, "DataType_SRational")
				case "FLOAT":
					types = append(types, "DataType_Float")
				case "DOUBLE":
					types = append(types, "DataType_Double")
				case "IFD":
					types = append(types, "DataType_IFD")
				case "UNICODE":
					types = append(types, "DataType_Unicode")
				case "COMPLEX":
					types = append(types, "DataType_Complex")
				case "LONG8":
					types = append(types, "DataType_Long8")
				case "SLONG8":
					types = append(types, "DataType_SLong8")
				case "IFD8":
					types = append(types, "DataType_IFD8")
				}
			}
			p.TagTypeMap[typeName] = types
		}
		if len(ss) > 1 {
			var nums []int
			for _, numName := range strings.Split(strings.TrimSpace(ss[1]), "/") {
				if v, err := strconv.Atoi(numName); err == nil {
					nums = append(nums, v)
				}
			}
			p.TagNumMap[typeName] = nums
		}
	}
}

func (p *Type) GenMapCode() {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "var _%sTable = map[%s]string {\n", p.TypeName, p.TypeName)
	for _, s := range p.TypeList {
		if _, ok := p.TagIFDMap[s]; ok {
			continue
		}
		fmt.Fprintf(&buf, "\t%s: `%s`, %s\n", s, s, p.TypeCommentMap[s])
	}
	fmt.Fprintf(&buf, "}\n")

	if p.TypeName == "TagType" && len(p.TagTypeMap) > 0 {
		fmt.Fprintf(&buf, "\nvar _TagType_TypesTable = map[TagType][]DataType {\n")
		for _, s := range p.TypeList {
			if _, ok := p.TagIFDMap[s]; ok {
				continue
			}
			if v, ok := p.TagTypeMap[s]; ok && len(v) > 0 {
				fmt.Fprintf(&buf, "%s: []DataType{ ", s)
				for j := 0; j < len(v); j++ {
					fmt.Fprintf(&buf, `%s, `, v[j])
				}
				fmt.Fprintf(&buf, "},\n")
			}
		}
		fmt.Fprintf(&buf, "}\n")

	}
	if p.TypeName == "TagType" && len(p.TagNumMap) > 0 {
		fmt.Fprintf(&buf, "\nvar _TagType_NumsTable = map[TagType][]int {\n")
		for _, s := range p.TypeList {
			if _, ok := p.TagIFDMap[s]; ok {
				continue
			}
			if v, ok := p.TagNumMap[s]; ok && len(v) > 0 {
				fmt.Fprintf(&buf, "%s: []int{ ", s)
				for j := 0; j < len(v); j++ {
					fmt.Fprintf(&buf, `%d, `, v[j])
				}
				fmt.Fprintf(&buf, "},\n")
			}
		}
		fmt.Fprintf(&buf, "}\n")
	}

	if p.TypeName == "TagType" {
		fmt.Fprintf(&buf, "\ntype TagGetter interface {\n")
		for _, s := range p.TypeList {
			if _, ok := p.TagIFDMap[s]; ok {
				continue
			}
			if _, ingore := p.TagIngoreMap[s]; !ingore {
				fmt.Fprintf(&buf, "Get%s() (value %s, ok bool)\n", s[len("TagType_"):], p.getValueType(s))
			}
		}
		fmt.Fprintf(&buf, "\n")
		fmt.Fprintf(&buf, "GetUnknown(tag TagType) (value []byte, ok bool)\n")
		fmt.Fprintf(&buf, "\n")
		fmt.Fprintf(&buf, "private()\n")
		fmt.Fprintf(&buf, "}\n")

		fmt.Fprintf(&buf, "\ntype TagSetter interface {\n")
		for _, s := range p.TypeList {
			if _, ok := p.TagIFDMap[s]; ok {
				continue
			}
			if _, ingore := p.TagIngoreMap[s]; !ingore {
				fmt.Fprintf(&buf, "Set%s(value %s) (ok bool)\n", s[len("TagType_"):], p.getValueType(s))
			}
		}
		fmt.Fprintf(&buf, "\n")
		fmt.Fprintf(&buf, "SetUnknown(tag TagType, value interface{}) (ok bool)\n")
		fmt.Fprintf(&buf, "\n")
		fmt.Fprintf(&buf, "private()\n")
		fmt.Fprintf(&buf, "}\n")
	}

	fmt.Fprintf(&buf, `
func (p %s) String() string {
	if name, ok := _%sTable[p]; ok {
		return name
	}
	return fmt.Sprintf("%s_Unknown(%%d)", uint16(p))
}
`,
		p.TypeName,
		p.TypeName,
		p.TypeName,
	)

	p.MapCode = buf.String()
}

func (p *Type) getValueType(typeName string) string {
	switch typeName {
	case "TagType_Compression":
		return "TagValue_CompressionType"
	case "TagType_PhotometricInterpretation":
		return "TagValue_PhotometricType"
	case "TagType_Predictor":
		return "TagValue_PredictorType"
	case "TagType_ResolutionUnit":
		return "TagValue_ResolutionUnitType"
	case "TagType_ColorMap":
		return "[][3]uint16"
	case "TagType_SMinSampleValue", "TagType_SMaxSampleValue":
		return "[]float64"
	case "TagType_DateTime":
		return "time.Time"
	}
	switch types, _ := p.TagTypeMap[typeName]; {
	case p.isIntType(types):
		if p.isOnlyOneValue(p.TagNumMap[typeName]) {
			return `int64`
		} else {
			return `[]int64`
		}
	case p.isFloatType(types):
		if p.isOnlyOneValue(p.TagNumMap[typeName]) {
			return `float64`
		} else {
			return `[]float64`
		}
	case p.isRationalType(types):
		if p.isOnlyOneValue(p.TagNumMap[typeName]) {
			return `[2]int64`
		} else {
			return `[][2]int64`
		}
	case p.isComplexType(types):
		if p.isOnlyOneValue(p.TagNumMap[typeName]) {
			return `complex128`
		} else {
			return `[]complex128`
		}
	case p.isStringType(types):
		return `string`
	case p.isUndefinedType(types):
		return `[]byte`
	default:
		return `[]byte`
	}
}

func (p *Type) isOnlyOneValue(nums []int) bool {
	return len(nums) == 1 && nums[0] == 1
}

func (p *Type) isIntType(dataTypes []string) bool {
	if len(dataTypes) == 0 {
		return false
	}
	for _, s := range dataTypes {
		switch s {
		case "DataType_Byte", "DataType_Short", "DataType_Long", "DataType_Long8":
		case "DataType_SByte", "DataType_SShort", "DataType_SLong", "DataType_SLong8":
		case "DataType_IFD", "DataType_IFD8":
		default:
			return false
		}
	}
	return true
}
func (p *Type) isFloatType(dataTypes []string) bool {
	if len(dataTypes) == 0 {
		return false
	}
	for _, s := range dataTypes {
		if s != "DataType_Float" && s != "DataType_Double" {
			return false
		}
	}
	return true
}
func (p *Type) isRationalType(dataTypes []string) bool {
	if len(dataTypes) == 0 {
		return false
	}
	for _, s := range dataTypes {
		if s != "DataType_Rational" && s != "DataType_SRational" {
			return false
		}
	}
	return true
}
func (p *Type) isComplexType(dataTypes []string) bool {
	if len(dataTypes) == 0 {
		return false
	}
	for _, s := range dataTypes {
		if s != "DataType_Complex" {
			return false
		}
	}
	return true
}
func (p *Type) isStringType(dataTypes []string) bool {
	if len(dataTypes) == 0 {
		return false
	}
	for _, s := range dataTypes {
		if s != "DataType_ASCII" && s != "DataType_Unicode" {
			return false
		}
	}
	return true
}
func (p *Type) isUndefinedType(dataTypes []string) bool {
	if len(dataTypes) == 0 {
		return true
	}
	for _, s := range dataTypes {
		if s == "DataType_Undefined" {
			return true
		}
	}
	return false
}
