package meta

import (
	"encoding/xml"
	"os"
	"strings"
	"time"

	"github.com/photoprism/photoprism/pkg/txt"
)

// XmpDocument represents an XMP sidecar file.
type XmpDocument struct {
	XMLName xml.Name `xml:"xmpmeta" json:"xmpmeta,omitempty"`
	Text    string   `xml:",chardata" json:"text,omitempty"`
	X       string   `xml:"x,attr" json:"x,omitempty"`
	Xmptk   string   `xml:"xmptk,attr" json:"xmptk,omitempty"`
	RDF     struct {
		Text        string `xml:",chardata" json:"text,omitempty"`
		Rdf         string `xml:"rdf,attr" json:"rdf,omitempty"`
		Description struct {
			Text            string `xml:",chardata" json:"text,omitempty"`
			About           string `xml:"about,attr" json:"about,omitempty"`
			Xmp             string `xml:"xmp,attr" json:"xmp,omitempty"`
			Aux             string `xml:"aux,attr" json:"aux,omitempty"`
			ExifEX          string `xml:"exifEX,attr" json:"exifex,omitempty"`
			Photoshop       string `xml:"photoshop,attr" json:"photoshop,omitempty"`
			XmpMM           string `xml:"xmpMM,attr" json:"xmpmm,omitempty"`
			Dc              string `xml:"dc,attr" json:"dc,omitempty"`
			Tiff            string `xml:"tiff,attr" json:"tiff,omitempty"`
			Exif            string `xml:"exif,attr" json:"exif,omitempty"`
			XmpRights       string `xml:"xmpRights,attr" json:"xmprights,omitempty"`
			Iptc4xmpCore    string `xml:"Iptc4xmpCore,attr" json:"iptc4xmpcore,omitempty"`
			Iptc4xmpExt     string `xml:"Iptc4xmpExt,attr" json:"iptc4xmpext,omitempty"`
			CreatorTool     string `xml:"CreatorTool"`     // ELE-L29 10.0.0.168(C431E2...
			ModifyDate      string `xml:"ModifyDate"`      // 2020-01-01T17:28:23.89961...
			CreateDate      string `xml:"CreateDate"`      // 2020-01-01T17:28:23
			MetadataDate    string `xml:"MetadataDate"`    // 2020-01-01T17:28:23.89961...
			Rating          string `xml:"Rating"`          // 4
			Lens            string `xml:"Lens"`            // HUAWEI P30 Rear Main Came...
			LensModel       string `xml:"LensModel"`       // HUAWEI P30 Rear Main Came...
			DateCreated     string `xml:"DateCreated"`     // 2020-01-01T17:28:25.72962...
			ColorMode       string `xml:"ColorMode"`       // 3
			ICCProfile      string `xml:"ICCProfile"`      // sRGB IEC61966-2.1
			AuthorsPosition string `xml:"AuthorsPosition"` // Maintainer
			DocumentID      string `xml:"DocumentID"`      // 2C678C1811D7095FD79CC822B...
			InstanceID      string `xml:"InstanceID"`      // 2C678C1811D7095FD79CC822B...
			Format          string `xml:"format"`          // image/jpeg
			Title           struct {
				Text string `xml:",chardata" json:"text,omitempty"`
				Alt  struct {
					Text string `xml:",chardata" json:"text,omitempty"`
					Li   struct {
						Text string `xml:",chardata" json:"text,omitempty"` // Night Shift / Berlin / 20...
						Lang string `xml:"lang,attr" json:"lang,omitempty"`
					} `xml:"li" json:"li,omitempty"`
				} `xml:"Alt" json:"alt,omitempty"`
			} `xml:"title" json:"title,omitempty"`
			Creator struct {
				Text string `xml:",chardata" json:"text,omitempty"`
				Seq  struct {
					Text string `xml:",chardata" json:"text,omitempty"`
					Li   string `xml:"li"` // Michael Mayer
				} `xml:"Seq" json:"seq,omitempty"`
			} `xml:"creator" json:"creator,omitempty"`
			Description struct {
				Text string `xml:",chardata" json:"text,omitempty"`
				Alt  struct {
					Text string `xml:",chardata" json:"text,omitempty"`
					Li   struct {
						Text string `xml:",chardata" json:"text,omitempty"` // Example file for developm...
						Lang string `xml:"lang,attr" json:"lang,omitempty"`
					} `xml:"li" json:"li,omitempty"`
				} `xml:"Alt" json:"alt,omitempty"`
			} `xml:"description" json:"description,omitempty"`
			Subject struct {
				Text string `xml:",chardata" json:"text,omitempty"`
				Bag  struct {
					Text string   `xml:",chardata" json:"text,omitempty"`
					Li   []string `xml:"li"` // desk, coffee, computer
				} `xml:"Bag" json:"bag,omitempty"`
				Seq struct {
					Text string   `xml:",chardata" json:"text,omitempty"`
					Li   []string `xml:"li"` // desk, coffee, computer
				} `xml:"Seq" json:"seq,omitempty"`
			} `xml:"subject" json:"subject,omitempty"`
			Rights struct {
				Text string `xml:",chardata" json:"text,omitempty"`
				Alt  struct {
					Text string `xml:",chardata" json:"text,omitempty"`
					Li   struct {
						Text string `xml:",chardata" json:"text,omitempty"` // This is an (edited) legal...
						Lang string `xml:"lang,attr" json:"lang,omitempty"`
					} `xml:"li" json:"li,omitempty"`
				} `xml:"Alt" json:"alt,omitempty"`
			} `xml:"rights" json:"rights,omitempty"`
			ImageWidth    string `xml:"ImageWidth"`  // 3648
			ImageLength   string `xml:"ImageLength"` // 2736
			BitsPerSample struct {
				Text string `xml:",chardata" json:"text,omitempty"`
				Seq  struct {
					Text string   `xml:",chardata" json:"text,omitempty"`
					Li   []string `xml:"li"` // 8
				} `xml:"Seq" json:"seq,omitempty"`
			} `xml:"BitsPerSample" json:"bitspersample,omitempty"`
			PhotometricInterpretation string `xml:"PhotometricInterpretation"` // 2
			Orientation               string `xml:"Orientation"`               // 0
			SamplesPerPixel           string `xml:"SamplesPerPixel"`           // 3
			YCbCrPositioning          string `xml:"YCbCrPositioning"`          // 1
			XResolution               string `xml:"XResolution"`               // 72/1
			YResolution               string `xml:"YResolution"`               // 72/1
			ResolutionUnit            string `xml:"ResolutionUnit"`            // 2
			Make                      string `xml:"Make"`                      // HUAWEI
			Model                     string `xml:"Model"`                     // ELE-L29
			ExifVersion               string `xml:"ExifVersion"`               // 0210
			FlashpixVersion           string `xml:"FlashpixVersion"`           // 0100
			ColorSpace                string `xml:"ColorProfile"`              // 1
			ComponentsConfiguration   struct {
				Text string `xml:",chardata" json:"text,omitempty"`
				Seq  struct {
					Text string   `xml:",chardata" json:"text,omitempty"`
					Li   []string `xml:"li"` // 1, 2, 3, 0
				} `xml:"Seq" json:"seq,omitempty"`
			} `xml:"ComponentsConfiguration" json:"componentsconfiguration,omitempty"`
			CompressedBitsPerPixel string `xml:"CompressedBitsPerPixel"` // 95/100
			PixelXDimension        string `xml:"PixelXDimension"`        // 3648
			PixelYDimension        string `xml:"PixelYDimension"`        // 2736
			DateTimeOriginal       string `xml:"DateTimeOriginal"`       // 2020-01-01T17:28:23
			ExposureTime           string `xml:"ExposureTime"`           // 20000000/1000000000
			FNumber                string `xml:"FNumber"`                // 180/100
			ExposureProgram        string `xml:"ExposureProgram"`        // 2
			ISOSpeedRatings        struct {
				Text string `xml:",chardata" json:"text,omitempty"`
				Seq  struct {
					Text string `xml:",chardata" json:"text,omitempty"`
					Li   string `xml:"li"` // 200
				} `xml:"Seq" json:"seq,omitempty"`
			} `xml:"ISOSpeedRatings" json:"isospeedratings,omitempty"`
			ShutterSpeedValue string `xml:"ShutterSpeedValue"` // 298973/10000
			ApertureValue     string `xml:"ApertureValue"`     // 1695994/1000000
			BrightnessValue   string `xml:"BrightnessValue"`   // 0/1
			ExposureBiasValue string `xml:"ExposureBiasValue"` // 0/10
			MaxApertureValue  string `xml:"MaxApertureValue"`  // 169/100
			MeteringMode      string `xml:"MeteringMode"`      // 5
			LightSource       string `xml:"LightSource"`       // 1
			Flash             struct {
				Text       string `xml:",chardata" json:"text,omitempty"`
				ParseType  string `xml:"parseType,attr" json:"parsetype,omitempty"`
				Fired      string `xml:"Fired"`      // False
				Return     string `xml:"Return"`     // 0
				Mode       string `xml:"Mode"`       // 0
				Function   string `xml:"Function"`   // False
				RedEyeMode string `xml:"RedEyeMode"` // False
			} `xml:"Flash" json:"flash,omitempty"`
			FocalLength           string `xml:"FocalLength"`           // 5580/1000
			SensingMethod         string `xml:"SensingMethod"`         // 2
			FileSource            string `xml:"FileSource"`            // 3
			SceneType             string `xml:"SceneType"`             // 1
			CustomRendered        string `xml:"CustomRendered"`        // 1
			ExposureMode          string `xml:"ExposureMode"`          // 0
			WhiteBalance          string `xml:"WhiteBalance"`          // 0
			DigitalZoomRatio      string `xml:"DigitalZoomRatio"`      // 100/100
			FocalLengthIn35mmFilm string `xml:"FocalLengthIn35mmFilm"` // 27
			SceneCaptureType      string `xml:"SceneCaptureType"`      // 0
			GainControl           string `xml:"GainControl"`           // 0
			Contrast              string `xml:"Contrast"`              // 0
			Saturation            string `xml:"Saturation"`            // 0
			Sharpness             string `xml:"Sharpness"`             // 0
			SubjectDistanceRange  string `xml:"SubjectDistanceRange"`  // 0
			SubSecTime            string `xml:"SubSecTime"`            // 899614
			SubSecTimeOriginal    string `xml:"SubSecTimeOriginal"`    // 899614
			SubSecTimeDigitized   string `xml:"SubSecTimeDigitized"`   // 899614
			GPSVersionID          string `xml:"GPSVersionID"`          // 2.2.0.0
			GPSLatitude           string `xml:"GPSLatitude"`           // 52,27.5814N
			GPSLongitude          string `xml:"GPSLongitude"`          // 13,19.3099E
			GPSAltitudeRef        string `xml:"GPSAltitudeRef"`        // 1
			GPSAltitude           string `xml:"GPSAltitude"`           // 0/100
			GPSTimeStamp          string `xml:"GPSTimeStamp"`          // 2020-01-01T16:28:22Z
			Marked                string `xml:"Marked"`                // False
			WebStatement          string `xml:"WebStatement"`          // http://docs.photoprism.or...
			CreatorContactInfo    struct {
				Text        string `xml:",chardata" json:"text,omitempty"`
				ParseType   string `xml:"parseType,attr" json:"parsetype,omitempty"`
				CiAdrExtadr string `xml:"CiAdrExtadr"` // Zimmermannstr. 37
				CiAdrCity   string `xml:"CiAdrCity"`   // Berlin
				CiAdrPcode  string `xml:"CiAdrPcode"`  // 12163
				CiAdrCtry   string `xml:"CiAdrCtry"`   // Germany
				CiTelWork   string `xml:"CiTelWork"`   // +49123456789
				CiEmailWork string `xml:"CiEmailWork"` // hello@photoprism.org
				CiUrlWork   string `xml:"CiUrlWork"`   // https://photoprism.org/
			} `xml:"CreatorContactInfo" json:"creatorcontactinfo,omitempty"`
			PersonInImage struct {
				Text string `xml:",chardata" json:"text,omitempty"`
				Bag  struct {
					Text string `xml:",chardata" json:"text,omitempty"`
					Li   string `xml:"li"` // Gopher
				} `xml:"Bag" json:"bag,omitempty"`
			} `xml:"PersonInImage" json:"personinimage,omitempty"`
		} `xml:"Description" json:"description,omitempty"`
	} `xml:"RDF" json:"rdf,omitempty"`
}

// Load parses an XMP file and populates document values with its contents.
func (doc *XmpDocument) Load(filename string) error {
	data, err := os.ReadFile(filename)

	if err != nil {
		return err
	}

	return xml.Unmarshal(data, doc)
}

// Title returns the XMP document title.
func (doc *XmpDocument) Title() string {
	t := doc.RDF.Description.Title.Alt.Li.Text
	t2 := doc.RDF.Description.Title.Text
	if t != "" {
		return SanitizeTitle(t)
	} else if t2 != "" {
		return SanitizeTitle(t2)
	}
	return ""
}

// Artist returns the XMP document artist.
func (doc *XmpDocument) Artist() string {
	return SanitizeString(doc.RDF.Description.Creator.Seq.Li)
}

// Description returns the XMP document description.
func (doc *XmpDocument) Description() string {
	d := doc.RDF.Description.Description.Alt.Li.Text
	d2 := doc.RDF.Description.Description.Text
	if d != "" {
		return SanitizeDescription(d)
	} else if d2 != "" {
		return SanitizeTitle(d2)
	}
	return ""
}

// Copyright returns the XMP document copyright info.
func (doc *XmpDocument) Copyright() string {
	return SanitizeString(doc.RDF.Description.Rights.Alt.Li.Text)
}

// CameraMake returns the XMP document camera make name.
func (doc *XmpDocument) CameraMake() string {
	return SanitizeString(doc.RDF.Description.Make)
}

// CameraModel returns the XMP document camera model name.
func (doc *XmpDocument) CameraModel() string {
	return SanitizeString(doc.RDF.Description.Model)
}

// LensModel returns the XMP document lens model name.
func (doc *XmpDocument) LensModel() string {
	return SanitizeString(doc.RDF.Description.LensModel)
}

// TakenAt returns the XMP document taken date.
func (doc *XmpDocument) TakenAt(timeZone string) time.Time {
	taken := time.Time{} // Unknown

	s := SanitizeString(doc.RDF.Description.DateCreated)

	if s == "" {
		return taken
	}

	if dateTime := txt.DateTime(s, timeZone); !dateTime.IsZero() {
		return dateTime
	}

	return taken
}

// Keywords returns the XMP document keywords.
func (doc *XmpDocument) Keywords() string {
	s := doc.RDF.Description.Subject.Seq.Li

	return strings.Join(s, ", ")
}
