package ps

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

// Document represents a Photoshop document (PSD file).
type Document struct {
	name      string
	height    int
	width     int
	artLayers []*ArtLayer
	layerSets []*LayerSet
}

// DocumentJSON is an exported version of Document that
// allows Documents to be saved to and loaded from JSON.
type DocumentJSON struct {
	Name      string
	Height    int
	Width     int
	ArtLayers []*ArtLayer
	LayerSets []*LayerSet
}

// MarshalJSON returns the Document in JSON format.
func (d *Document) MarshalJSON() ([]byte, error) {
	return json.Marshal(&DocumentJSON{Name: d.name, Height: d.height,
		Width: d.width, ArtLayers: d.artLayers, LayerSets: d.layerSets})
}

// UnmarshalJSON loads JSON data into this Document.
func (d *Document) UnmarshalJSON(b []byte) error {
	tmp := &DocumentJSON{}
	if err := json.Unmarshal(b, &tmp); err != nil {
		return err
	}
	d.name = tmp.Name
	d.height = tmp.Height
	d.width = tmp.Width
	d.artLayers = tmp.ArtLayers
	for _, lyr := range d.artLayers {
		lyr.SetParent(d)
	}
	d.layerSets = tmp.LayerSets
	for _, set := range d.layerSets {
		set.SetParent(d)
	}
	return nil
}

// Name returns the document's title.
// This fufills the Group interface.
func (d *Document) Name() string {
	return d.name
}

// Parent returns the Group that contains d.
func (d *Document) Parent() Group {
	return nil
}

// Height returns the height of the document, in pixels.
func (d *Document) Height() int {
	return d.height
}

// ArtLayer returns the first top level ArtLayer matching
// the given name.
func (d *Document) ArtLayer(name string) *ArtLayer {
	for _, lyr := range d.artLayers {
		if lyr.name == name {
			if Mode == 0 && !lyr.current {
				err := lyr.Refresh()
				if err != nil {
					log.Panic(err)
				}
			}
			return lyr
		}
	}
	return nil
}

// ArtLayers returns this document's ArtLayers, if any.
func (d *Document) ArtLayers() []*ArtLayer {
	return d.artLayers
}

// LayerSets returns all the document's top level LayerSets.
func (d *Document) LayerSets() []*LayerSet {
	return d.layerSets
}

// LayerSet returns the first top level LayerSet matching
// the given name.
func (d *Document) LayerSet(name string) *LayerSet {
	for _, set := range d.layerSets {
		if set.name == name {
			if Mode != Fast && !set.current {
				if err := set.Refresh(); err != nil {
					log.Panic(err)
				}
			}
			return set
		}
	}
	return nil
}

// ActiveDocument returns document currently focused in Photoshop.
//
// TODO(sbrow): Reduce cylcomatic complexity of ActiveDocument().
func ActiveDocument() (*Document, error) {
	log.Println("Loading ActiveDoucment")
	d := &Document{}

	byt, err := DoJS("activeDocName.jsx")
	if err != nil {
		return nil, err
	}
	d.name = strings.TrimRight(string(byt), "\r\n")
	if Mode != Safe {
		err = d.Restore()
		switch {
		case os.IsNotExist(err):
			log.Println("Previous version not found.")
		case err == nil:
			return d, err
		default:
			return nil, err

		}
	}
	log.Println("Loading manually (This could take awhile)")
	byt, err = DoJS("getActiveDoc.jsx")
	if err != nil {
		return nil, err
	}
	if err = json.Unmarshal(byt, &d); err != nil {
		d.Dump()
		return nil, err
	}
	for _, lyr := range d.artLayers {
		lyr.SetParent(d)
	}
	for i, set := range d.layerSets {
		var s *LayerSet
		if s, err = NewLayerSet(set.Path()+"/", d); err != nil {
			return nil, err
		}
		d.layerSets[i] = s
		s.SetParent(d)
	}
	d.Dump()
	return d, err
}

// Restore loads document data from a JSON file.
func (d *Document) Restore() error {
	byt, err := ioutil.ReadFile(d.Filename())
	if err == nil {
		log.Println("Previous version found, loading")
		err = json.Unmarshal(byt, &d)
	}
	return err
}

// SetParent does nothing, as the document is a top-level object
// and therefore can't have a parent group.
// The function is needed to implement the group interface.
func (d *Document) SetParent(g Group) {}

// Path returns the root path ("") for all the layers.
func (d *Document) Path() string {
	return ""
}

// Filename returns the path to the json file for this document.
func (d *Document) Filename() string {
	_, dir, _, ok := runtime.Caller(0)
	if !ok {
		log.Panic("No caller information")
	}
	return filepath.Join(filepath.Dir(dir), "data",
		strings.TrimRight(d.name, "\r\n")+".txt")
}

// Dump saves the document to disk in JSON format.
func (d *Document) Dump() {
	log.Println("Dumping to disk")
	log.Println(d.Filename())
	f, err := os.Create(d.Filename())
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		if err = f.Close(); err != nil {
			log.Println(err)
		}
	}()
	byt, err := json.MarshalIndent(d, "", "\t")
	if err != nil {
		log.Fatal(err)
	}
	if _, err = f.Write(byt); err != nil {
		log.Println(err)
	}
}

// MustExist returns a Layer from the set with the given name, and
// panics if it doesn't exist.
//
// If there is a LayerSet and an  ArtLayer with the same name,
// it will return the LayerSet.
func (d *Document) MustExist(name string) Layer {
	set := d.LayerSet(name)
	if set == nil {
		lyr := d.ArtLayer(name)
		if lyr == nil {
			log.Panicf("no Layer found at \"%s%s\"", d.Path(), name)
		}
		return lyr
	}
	return set
}
