// Copyright 2017 The Hugo Authors. All rights reserved.
//
// 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 hugolib

import (
	"fmt"
	"path"
	"strconv"
	"strings"

	"github.com/gohugoio/hugo/helpers"
	radix "github.com/hashicorp/go-immutable-radix"
)

// Deprecated: Use .Site.Home.Sections.
// TODO(bep) Hugo 0.23 = Reuse as an alias for home's sections.
func (s *SiteInfo) Sections() Taxonomy {

	helpText := `In Hugo 0.22 we introduced nested sections, so this method now returns an empty taxonomy.
	
To list sections with its pages, you can do something like this:

{{ range $.Site.Home.Sections }}
  Section: {{ .Title }}
  {{ range .Pages }}
     Section Page: {{ .Title }}
  {{ end }}
{{ end }}

To get a specific section, you can do this:

{{ $section := $.Site.GetPage "section" "blog" }}
`

	helpers.Deprecated("Site", "Sections", helpText, true)

	return Taxonomy{}
}

// Home is a shortcut to the home page, equivalent to .Site.GetPage "home".
func (s *SiteInfo) Home() (*Page, error) {
	return s.GetPage(KindHome)
}

// Parent returns a section's parent section or a page's section.
// To get a section's subsections, see Page's Sections method.
func (p *Page) Parent() *Page {
	return p.parent
}

// current returns the page's current section.
// Note that this will return nil for pages that is not regular, home or section pages.
// Note that for paginated sections and home pages, this will return the original page pointer.
func (p *Page) current() *Page {
	v := p
	if v.origOnCopy != nil {
		v = v.origOnCopy
	}
	if v.IsHome() || v.IsSection() {
		return v
	}

	return v.parent
}

// InSection returns whether the given page is in the current section.
// Note that this will always return false for pages that are
// not either regular, home or section pages.
func (p *Page) InSection(other interface{}) (bool, error) {
	if p == nil || other == nil {
		return false, nil
	}

	if po, ok := other.(*PageOutput); ok {
		other = po.Page
	}

	pp, ok := other.(*Page)
	if !ok {
		return false, fmt.Errorf("%T not supported in InSection", other)
	}

	if pp == nil {
		return false, nil
	}

	return pp.current() == p.current(), nil
}

// Sections returns this section's subsections, if any.
// Note that for non-sections, this method will always return an empty list.
func (p *Page) Sections() Pages {
	return p.subSections
}

func (s *Site) assembleSections() Pages {
	var newPages Pages

	if !s.isEnabled(KindSection) {
		return newPages
	}

	// Maps section kind pages to their path, i.e. "my/section"
	sectionPages := make(map[string]*Page)

	// The sections with content files will already have been created.
	for _, sect := range s.findPagesByKind(KindSection) {
		sectionPages[path.Join(sect.sections...)] = sect
	}

	const (
		sectKey     = "__hs"
		sectSectKey = "_a" + sectKey
		sectPageKey = "_b" + sectKey
	)

	var (
		home       *Page
		inPages    = radix.New().Txn()
		inSections = radix.New().Txn()
		undecided  Pages
	)

	for i, p := range s.Pages {
		if p.Kind != KindPage {
			if p.Kind == KindHome {
				home = p
			}
			continue
		}

		if len(p.sections) == 0 {
			// Root level pages. These will have the home page as their Parent.
			p.parent = home
			continue
		}

		sectionKey := path.Join(p.sections...)
		sect, found := sectionPages[sectionKey]

		if !found && len(p.sections) == 1 {
			// We only create content-file-less sections for the root sections.
			sect = s.newSectionPage(p.sections[0])
			sectionPages[sectionKey] = sect
			newPages = append(newPages, sect)
			found = true
		}

		if len(p.sections) > 1 {
			// Create the root section if not found.
			_, rootFound := sectionPages[p.sections[0]]
			if !rootFound {
				sect = s.newSectionPage(p.sections[0])
				sectionPages[p.sections[0]] = sect
				newPages = append(newPages, sect)
			}
		}

		if found {
			pagePath := path.Join(sectionKey, sectPageKey, strconv.Itoa(i))
			inPages.Insert([]byte(pagePath), p)
		} else {
			undecided = append(undecided, p)
		}
	}

	// Create any missing sections in the tree.
	// A sub-section needs a content file, but to create a navigational tree,
	// given a content file in /content/a/b/c/_index.md, we cannot create just
	// the c section.
	for _, sect := range sectionPages {
		for i := len(sect.sections); i > 0; i-- {
			sectionPath := sect.sections[:i]
			sectionKey := path.Join(sectionPath...)
			sect, found := sectionPages[sectionKey]
			if !found {
				sect = s.newSectionPage(sectionPath[len(sectionPath)-1])
				sect.sections = sectionPath
				sectionPages[sectionKey] = sect
				newPages = append(newPages, sect)
			}
		}
	}

	for k, sect := range sectionPages {
		inPages.Insert([]byte(path.Join(k, sectSectKey)), sect)
		inSections.Insert([]byte(k), sect)
	}

	var (
		currentSection *Page
		children       Pages
		rootSections   = inSections.Commit().Root()
	)

	for i, p := range undecided {
		// Now we can decide where to put this page into the tree.
		sectionKey := path.Join(p.sections...)
		_, v, _ := rootSections.LongestPrefix([]byte(sectionKey))
		sect := v.(*Page)
		pagePath := path.Join(path.Join(sect.sections...), sectSectKey, "u", strconv.Itoa(i))
		inPages.Insert([]byte(pagePath), p)
	}

	var rootPages = inPages.Commit().Root()

	rootPages.Walk(func(path []byte, v interface{}) bool {
		p := v.(*Page)

		if p.Kind == KindSection {
			if currentSection != nil {
				// A new section
				currentSection.setPagePages(children)
			}

			currentSection = p
			children = make(Pages, 0)

			return false

		}

		// Regular page
		p.parent = currentSection
		children = append(children, p)
		return false
	})

	if currentSection != nil {
		currentSection.setPagePages(children)
	}

	// Build the sections hierarchy
	for _, sect := range sectionPages {
		if len(sect.sections) == 1 {
			sect.parent = home
		} else {
			parentSearchKey := path.Join(sect.sections[:len(sect.sections)-1]...)
			_, v, _ := rootSections.LongestPrefix([]byte(parentSearchKey))
			p := v.(*Page)
			sect.parent = p
		}

		if sect.parent != nil {
			sect.parent.subSections = append(sect.parent.subSections, sect)
		}
	}

	var (
		sectionsParamId      = "mainSections"
		sectionsParamIdLower = strings.ToLower(sectionsParamId)
		mainSections         interface{}
		mainSectionsFound    bool
		maxSectionWeight     int
	)

	mainSections, mainSectionsFound = s.Info.Params[sectionsParamIdLower]

	for _, sect := range sectionPages {
		if sect.parent != nil {
			sect.parent.subSections.Sort()
		}

		for i, p := range sect.Pages {
			if i > 0 {
				p.NextInSection = sect.Pages[i-1]
			}
			if i < len(sect.Pages)-1 {
				p.PrevInSection = sect.Pages[i+1]
			}
		}

		if !mainSectionsFound {
			weight := len(sect.Pages) + (len(sect.Sections()) * 5)
			if weight >= maxSectionWeight {
				mainSections = []string{sect.Section()}
				maxSectionWeight = weight
			}
		}
	}

	// Try to make this as backwards compatible as possible.
	s.Info.Params[sectionsParamId] = mainSections
	s.Info.Params[sectionsParamIdLower] = mainSections

	return newPages

}

func (p *Page) setPagePages(pages Pages) {
	pages.Sort()
	p.Pages = pages
	p.Data = make(map[string]interface{})
	p.Data["Pages"] = pages
}
