// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package po

import (
	"bytes"
	"fmt"
	"io"
	"strconv"
	"strings"
)

// A PO file is made up of many entries,
// each entry holding the relation between an original untranslated string
// and its corresponding translation.
//
// See http://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
type Message struct {
	Comment               // Coments
	MsgContext   string   // msgctxt context
	MsgId        string   // msgid untranslated-string
	MsgIdPlural  string   // msgid_plural untranslated-string-plural
	MsgStr       string   // msgstr translated-string
	MsgStrPlural []string // msgstr[0] translated-string-case-0
}

func (p *Message) less(q *Message) bool {
	if p.Comment.less(&q.Comment) {
		return true
	}
	if a, b := p.MsgContext, q.MsgContext; a != b {
		return a < b
	}
	if a, b := p.MsgId, q.MsgId; a != b {
		return a < b
	}
	if a, b := p.MsgIdPlural, q.MsgIdPlural; a != b {
		return a < b
	}
	return false
}

func (p *Message) readPoEntry(r *lineReader) (err error) {
	*p = Message{}
	if err = r.skipBlankLine(); err != nil {
		return
	}
	defer func(oldPos int) {
		newPos := r.currentPos()
		if newPos != oldPos && err == io.EOF {
			err = nil
		}
	}(r.currentPos())

	if err = p.Comment.readPoComment(r); err != nil {
		return
	}
	for {
		var s string
		if s, _, err = r.currentLine(); err != nil {
			return
		}

		if p.isInvalidLine(s) {
			err = fmt.Errorf("gettext: line %d, %v", r.currentPos(), "invalid line")
			return
		}
		if reComment.MatchString(s) || reBlankLine.MatchString(s) {
			return
		}

		if err = p.readMsgContext(r); err != nil {
			return
		}
		if err = p.readMsgId(r); err != nil {
			return
		}
		if err = p.readMsgIdPlural(r); err != nil {
			return
		}
		if err = p.readMsgStrOrPlural(r); err != nil {
			return
		}
	}
}

func (p *Message) readMsgContext(r *lineReader) (err error) {
	var s string
	if s, _, err = r.currentLine(); err != nil {
		return
	}
	if !reMsgContext.MatchString(s) {
		return
	}
	p.MsgContext, err = p.readString(r)
	return
}

func (p *Message) readMsgId(r *lineReader) (err error) {
	var s string
	if s, _, err = r.currentLine(); err != nil {
		return
	}
	if !reMsgId.MatchString(s) {
		return
	}
	p.MsgId, err = p.readString(r)
	return
}

func (p *Message) readMsgIdPlural(r *lineReader) (err error) {
	var s string
	if s, _, err = r.currentLine(); err != nil {
		return
	}
	if !reMsgIdPlural.MatchString(s) {
		return
	}
	p.MsgIdPlural, err = p.readString(r)
	return nil
}

func (p *Message) readMsgStrOrPlural(r *lineReader) (err error) {
	var s string
	if s, _, err = r.currentLine(); err != nil {
		return
	}
	if !reMsgStr.MatchString(s) && !reMsgStrPlural.MatchString(s) {
		return
	}
	if reMsgStrPlural.MatchString(s) {
		left, right := strings.Index(s, `[`), strings.LastIndex(s, `]`)
		idx, _ := strconv.Atoi(s[left+1 : right])
		s, err = p.readString(r)
		if n := len(p.MsgStrPlural); (idx + 1) > n {
			p.MsgStrPlural = append(p.MsgStrPlural, make([]string, (idx+1)-n)...)
		}
		p.MsgStrPlural[idx] = s
	} else {
		p.MsgStr, err = p.readString(r)
	}
	return nil
}

func (p *Message) readString(r *lineReader) (msg string, err error) {
	var s string
	if s, _, err = r.readLine(); err != nil {
		return
	}
	msg += decodePoString(s)
	for {
		if s, _, err = r.readLine(); err != nil {
			return
		}
		if !reStringLine.MatchString(s) {
			r.unreadLine()
			break
		}
		msg += decodePoString(s)
	}
	return
}

// String returns the po format entry string.
func (p Message) String() string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "%s", p.Comment.String())
	if p.MsgContext != "" {
		fmt.Fprintf(&buf, "msgctxt %s", encodePoString(p.MsgContext))
	}
	fmt.Fprintf(&buf, "msgid %s", encodePoString(p.MsgId))
	if p.MsgIdPlural != "" {
		fmt.Fprintf(&buf, "msgid_plural %s", encodePoString(p.MsgIdPlural))
	}
	if len(p.MsgStrPlural) == 0 {
		if p.MsgStr != "" {
			fmt.Fprintf(&buf, "msgstr %s", encodePoString(p.MsgStr))
		} else {
			fmt.Fprintf(&buf, "msgstr %s", `""`+"\n")
		}
	} else {
		for i := 0; i < len(p.MsgStrPlural); i++ {
			if p.MsgStrPlural[i] != "" {
				fmt.Fprintf(&buf, "msgstr[%d] %s", i, encodePoString(p.MsgStrPlural[i]))
			} else {
				fmt.Fprintf(&buf, "msgstr[%d] %s", i, `""`+"\n")
			}
		}
	}
	return buf.String()
}
