// Copyright 2011 The GoGL Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.mkd file.

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"regexp"
	"strings"
)

var (
	funcCommentLineRE     = regexp.MustCompile("^#")
	funcRE                = regexp.MustCompile("^(\\w+)\\([\\w\\s,]*\\)")
	funcReturnRE          = regexp.MustCompile("^return\\s+(\\w+)")
	funcParamRE           = regexp.MustCompile("^param\\s+(\\w+)\\s+(\\w+)\\s+(in|out)\\s+(array|value|reference)")
	funcVectorEquivRE     = regexp.MustCompile("^vectorequiv\\s+(\\w+)")
	funcGlxVectorEquivRE  = regexp.MustCompile("^glxvectorequiv\\s+(\\w+)")
	funcCategoryRE        = regexp.MustCompile("^category\\s+(\\w+)")
	funcSubCategoryRE     = regexp.MustCompile("^subcategory\\s+(\\w+)")
	funcVersionRE         = regexp.MustCompile("^version\\s+([0-9]+)\\.([0-9]+)")
	funcDeprecatedRE      = regexp.MustCompile("^deprecated\\s+([0-9]+)\\.([0-9]+)")
	funcOffsetRE          = regexp.MustCompile("^offset\\s+([0-9\\*\\?]+)")
	funcGlxRopCodeRE      = regexp.MustCompile("^glxropcode\\s+([0-9\\*\\?]+)")
	funcGlxSingleRE       = regexp.MustCompile("^glxsingle\\s+([0-9\\?\\*]+)")
	funcWglFlagsRE        = regexp.MustCompile("^wglflags(\\s+([\\w\\*\\- ]+))")
	funcGlxFlagsRE        = regexp.MustCompile("^glxflags(\\s+([\\w\\*\\- ]+))")
	funcDlFlagsRE         = regexp.MustCompile("^dlflags\\s+([\\w\\*\\- ]+)")
	funcGlfFlagsRE        = regexp.MustCompile("^glfflags\\s+([\\w\\*\\- ]+)")
	funcGlxVendorPrivRE   = regexp.MustCompile("^glxvendorpriv\\s+([0-9\\?]+)")
	funcGlextMaskRE       = regexp.MustCompile("^glextmask\\s+(\\w+)")
	funcExtensionRE       = regexp.MustCompile("^extension$|^extension\\s+([\\w\\- ]*)")
	funcAliasRE           = regexp.MustCompile("^alias\\s+(\\w+)")
	funcBeginEndRE        = regexp.MustCompile("^beginend\\s+([\\w\\*\\-]+)")
	funcAllCategoriesRE   = regexp.MustCompile("^category:\\s*([\\w ]+)")
	funcAllVersionsRE     = regexp.MustCompile("^version:\\s*([0-9\\. ]+)")
	funcAllDeprVersionsRE = regexp.MustCompile("^deprecated:\\s*([0-9\\. ]+)")
	funcPassthruRE        = regexp.MustCompile("^passthru:\\s*(.*)")
	funcNewCategoryRE     = regexp.MustCompile("^newcategory:\\s*(\\w+)")
	//funcIgnoreRE          = regexp.MustCompile("^[a-z\\-]+:.*")
)

func ReadFunctionsFromFiles(files []string) (FunctionCategories, *FunctionsInfo, error) {
	var finfo *FunctionsInfo = nil
	allFuncts := make(FunctionCategories)
	for _, file := range files {
		functs, info, err := ReadFunctionsFromFile(file)
		if err != nil {
			return nil, nil, err
		}
		for k, v := range functs {
			allFuncts[k] = v
		}
		if finfo == nil {
			finfo = info
		} else {
			finfo.Versions = append(finfo.Versions, info.Versions...)
			finfo.DeprecatedVersions = append(finfo.DeprecatedVersions, info.DeprecatedVersions...)
			finfo.Categories = append(finfo.Categories, info.Categories...)
			for k, v := range info.Passthru {
				finfo.Passthru[k] = append(finfo.Passthru[k], v...)
			}

		}
	}
	return allFuncts, finfo, nil
}

func ReadFunctionsFromFile(name string) (FunctionCategories, *FunctionsInfo, error) {
	file, err := os.Open(name)
	if err != nil {
		return nil, nil, err
	}
	defer file.Close()
	return ReadFunctions(file)
}

func ReadFunctions(r io.Reader) (FunctionCategories, *FunctionsInfo, error) {
	var currentFunction *Function = nil
	currentCategory := "global"
	functions := make(FunctionCategories)
	finfo := &FunctionsInfo{make([]Version, 0, 8), make([]Version, 0, 8), make([]string, 0, 8), make(map[string][]string)}

	br := bufio.NewReader(r)

	for {
		line, err := br.ReadString('\n')
		if err == io.EOF {
			break
		} else if err != nil {
			return nil, nil, err
		}
		line = strings.Trim(line, "\t\n\r ")

		if len(line) == 0 || enumCommentLineRE.MatchString(line) {
			//fmt.Printf("Empty or comment line %s\n", line)
			continue
		}

		if f := funcRE.FindStringSubmatch(line); f != nil {
			currentFunction = new(Function)
			currentFunction.Name = f[1]
			currentFunction.Parameters = make([]Parameter, 0, 4)
		} else if ret := funcReturnRE.FindStringSubmatch(line); ret != nil {
			currentFunction.Return = ret[1]
		} else if param := funcParamRE.FindStringSubmatch(line); param != nil {
			out := false
			if param[3] == "out" {
				out = true
			}
			modifier := ParamModifierValue
			if param[4] == "array" {
				modifier = ParamModifierArray
			} else if param[4] == "reference" {
				modifier = ParamModifierReference
			}
			currentFunction.Parameters = append(currentFunction.Parameters, Parameter{param[1], param[2], out, modifier})
		} else if category := funcCategoryRE.FindStringSubmatch(line); category != nil {
			currentCategory = category[1]
			currentFunction.Category = currentCategory
			if functions[currentCategory] == nil {
				functions[currentCategory] = make([]*Function, 0, 4)
			}
			functions[currentCategory] = append(functions[currentCategory], currentFunction)
		} else if subcategory := funcSubCategoryRE.FindStringSubmatch(line); subcategory != nil {
			currentFunction.SubCategory = subcategory[1]
		} else if version := funcVersionRE.FindStringSubmatch(line); version != nil {
			v, err := MakeVersionFromMajorMinorString(version[1], version[2])
			if err != nil {
				return functions, finfo, fmt.Errorf("Unable to parse version: '%s'", line)
			}
			currentFunction.Version = v
		} else if deprecated := funcDeprecatedRE.FindStringSubmatch(line); deprecated != nil {
			v, err := MakeVersionFromMajorMinorString(deprecated[1], deprecated[2])
			if err != nil {
				return functions, finfo, fmt.Errorf("Unable to parse version: '%s'", line)
			}
			currentFunction.DeprecatedVersion = v
		} else if offset := funcOffsetRE.FindStringSubmatch(line); offset != nil {
			currentFunction.Offset = offset[1]
		} else if glxropcode := funcGlxRopCodeRE.FindStringSubmatch(line); glxropcode != nil {
			currentFunction.GlxRopCode = glxropcode[1]
		} else if glxsingle := funcGlxSingleRE.FindStringSubmatch(line); glxsingle != nil {
			currentFunction.GlxSingle = glxsingle[1]
		} else if wglflags := funcWglFlagsRE.FindStringSubmatch(line); wglflags != nil {
			currentFunction.WglFlags = wglflags[1]
		} else if glxflags := funcGlxFlagsRE.FindStringSubmatch(line); glxflags != nil {
			currentFunction.GlxFlags = glxflags[1]
		} else if dlflags := funcDlFlagsRE.FindStringSubmatch(line); dlflags != nil {
			currentFunction.DlFlags = dlflags[1]
		} else if glfflags := funcGlfFlagsRE.FindStringSubmatch(line); glfflags != nil {
			currentFunction.GlfFlags = glfflags[1]
		} else if glxvendorpriv := funcGlxVendorPrivRE.FindStringSubmatch(line); glxvendorpriv != nil {
			currentFunction.GlxVendorPriv = glxvendorpriv[1]
		} else if glextmask := funcGlextMaskRE.FindStringSubmatch(line); glextmask != nil {
			currentFunction.GlextMask = glextmask[1]
		} else if extension := funcExtensionRE.FindStringSubmatch(line); extension != nil {
			if len(extension) >= 2 {
				currentFunction.Extension = extension[1]
			}
		} else if vectorequiv := funcVectorEquivRE.FindStringSubmatch(line); vectorequiv != nil {
			currentFunction.VectorEquiv = vectorequiv[1]
		} else if glxvectorequiv := funcGlxVectorEquivRE.FindStringSubmatch(line); glxvectorequiv != nil {
			currentFunction.GlxVectorEquiv = glxvectorequiv[1]
		} else if alias := funcAliasRE.FindStringSubmatch(line); alias != nil {
			currentFunction.Alias = alias[1]
		} else if beginend := funcBeginEndRE.FindStringSubmatch(line); beginend != nil {
			currentFunction.BeginEnd = beginend[1]
		} else if allVersions := funcAllVersionsRE.FindStringSubmatch(line); allVersions != nil {
			split := strings.Split(allVersions[1], " ")
			for _, verString := range split {
				v, err := MakeVersionFromString(verString)
				if err != nil {
					return functions, finfo, fmt.Errorf("Unable to parse version: '%s'", line)
				}
				finfo.Versions = append(finfo.Versions, v)
			}
		} else if allDeprVersions := funcAllDeprVersionsRE.FindStringSubmatch(line); allDeprVersions != nil {
			split := strings.Split(allDeprVersions[1], " ")
			for _, verString := range split {
				v, err := MakeVersionFromString(verString)
				if err != nil {
					return functions, finfo, fmt.Errorf("Unable to parse version: '%s'", line)
				}
				finfo.DeprecatedVersions = append(finfo.DeprecatedVersions, v)
			}
		} else if allCategories := funcAllCategoriesRE.FindStringSubmatch(line); allCategories != nil {
			finfo.Categories = strings.Split(allCategories[1], " ")
		} else if passthru := funcPassthruRE.FindStringSubmatch(line); passthru != nil {
			if _, ok := finfo.Passthru[currentCategory]; !ok {
				finfo.Passthru[currentCategory] = make([]string, 0, 8)
			}
			finfo.Passthru[currentCategory] = append(finfo.Passthru[currentCategory], passthru[1])
		} else if newcategory := funcNewCategoryRE.FindStringSubmatch(line); newcategory != nil {
			currentCategory = newcategory[1]
			functions[currentCategory] = make([]*Function, 0)
			//} else if funcIgnoreRE.MatchString(line) {
			//	// ignore
		} else {
			fmt.Fprintf(os.Stderr, "WARNING: Unable to parse line '%s' (Ignoring)\n", line)
			//return functions, finfo, fmt.Errorf("Unable to parse line: '%s'", line)
		}
	}

	// HACK: This is really ugly:
	// Some ARB extensions are now part of the GL core (starting from version 3.0).
	// Parse the passthru C comments for every "VERSION_*" category.
	// If we found an extension in the comment -> add them to a version category.
	for cat, pts := range finfo.Passthru {
		if strings.HasPrefix(cat, "VERSION") {
			for _, pt := range pts {
				strippedCat := strings.Split(strings.Trim(pt, "\t */"), " ")[0]
				if foundFuncts, ok := functions[strippedCat]; ok {
					fmt.Printf("Adding extension %s to %s\n", strippedCat, cat)
					newFuncts := make([]*Function, 0, len(foundFuncts))
					for _, f := range foundFuncts {
						// copy function and set the new category
						nf := *f
						nf.Category = cat
						newFuncts = append(newFuncts, &nf)
					}
					functions[cat] = append(functions[cat], newFuncts...)
				}
			}
		}
	}

	return functions, finfo, nil
}
