// Package cpedict defines the types and methods necessary to parse and lookup CPE dictionary conforming to
// CPE Dictionary specification 2.3 as per https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7697.pdf.
// The implementation is not full, only parts required to parse NVD vulnerability feed are implemented
//
// Copyright (c) Facebook, Inc. and its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cpedict

import (
	"encoding/xml"
	"io"
	"time"

	"github.com/facebookincubator/nvdtools/wfn"
)

// TextType represents multi-language text
type TextType map[string]string

// UnmarshalXML -- load TextType from XML
func (t *TextType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	var text string
	lang := "en"
	if *t == nil {
		*t = TextType{}
	}
	for _, attr := range start.Attr {
		if attr.Name.Local == "lang" {
			lang = attr.Value
		}
	}
	if err := d.DecodeElement(&text, &start); err != nil {
		return err
	}
	(*t)[lang] = text
	return nil
}

// PlatformType -- NVD doesn't use it
// TODO: implement
// type PlatformType struct{}

// CheckFactRefType is a reference to a check that always evaluates to
// TRUE, FALSE, or ERROR. Examples of types of checks are OVAL and OCIL checks.
// NVD doesn't use it
// TODO: implement
// type CheckFactRefType struct{}

// NamePattern represents CPE name
type NamePattern wfn.Attributes

// UnmarshalXMLAttr implements xml.UnmarshalerAttr interface
func (np *NamePattern) UnmarshalXMLAttr(attr xml.Attr) error {
	wfn, err := wfn.Parse(attr.Value)
	if err != nil {
		return err
	}
	*np = (NamePattern)(*wfn)
	return nil
}

func (np NamePattern) String() string {
	return wfn.Attributes(np).String()
}

// Reference holds additional information about CPE.
type Reference struct {
	URL  string `xml:"href,attr"`
	Desc string `xml:",chardata"`
}

// DeprecatedInfo contains the name that is deprecating the identifier name and the type of Deprecation
type DeprecatedInfo struct {
	Name NamePattern `xml:"name,attr"`
	Type string      `xml:"type,attr"`
}

// Deprecation contains the deprecation information for a specific deprecation of a given identifier name.
type Deprecation struct {
	Date         time.Time        `xml:"date,attr"`
	DeprecatedBy []DeprecatedInfo `xml:"deprecated-by"`
}

// CPE23Item contains all CPE 2.3 specific data related to a given identifier name.
type CPE23Item struct {
	Name        NamePattern  `xml:"name,attr"`
	Deprecation *Deprecation `xml:"deprecation"`
	// TODO: implement ProvenanceRecord
}

// CPEItem contains all of the information for a single dictionary entry (identifier name), including metadata.
type CPEItem struct {
	Name            NamePattern  `xml:"name,attr"`
	Deprecated      bool         `xml:"deprecated,attr"`
	DeprecatedBy    *NamePattern `xml:"deprecated_by,attr"`
	DeprecationDate time.Time    `xml:"deprecation_date,attr"`
	CPE23           CPE23Item    `xml:"cpe23-item"`
	Title           TextType     `xml:"title"`
	Notes           TextType     `xml:"notes"`
	References      []Reference  `xml:"references>reference"`
	// Calls out a check, such as an OVAL definition, that can confirm or reject
	// an IT system as an instance of the named platform. 0-n occurrences.
	// TODO: not implemented
	Check struct{} `xml:"check"`
}

// Generator contains information about the generation of the dictionary file.
type Generator struct {
	ProductName    string    `xml:"product_name"`
	ProductVersion string    `xml:"product_version"`
	SchemaVersion  string    `xml:"schema_version"`
	TimeStamp      time.Time `xml:"timestamp"`
}

// CPEList  contains all of the dictionary entries and dictionary metadata.
type CPEList struct {
	Generator Generator `xml:"generator"`
	Items     []CPEItem `xml:"cpe-item"`
}

// Decode decodes dictionary XML
func Decode(r io.Reader) (*CPEList, error) {
	var list CPEList
	if err := xml.NewDecoder(r).Decode(&list); err != nil {
		return nil, err
	}
	return &list, nil
}
