###
Copyright 2018 Scytl Secure Electronic Voting SA

All rights reserved

See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
###
log = (msg) ->
    # console.log msg


# Ballot items base class
class BallotItem

    # @param id {string} identifier
    constructor: (@id) ->
        @title = ''
        @description = ''

    # @return {String} fully qualified identifier
    getQualifiedId: () ->
        if @parent
            @parent.getQualifiedId() + '_' + @id
        else
            @id


# A selectable option, answer or choice
class Option extends BallotItem

    # @param rawData {Object} rawData to build the option
    # @param isBlank {boolean} true if this is a blank option
    constructor: (rawData, isBlank) ->
        @id = rawData.id
        @attribute = rawData.attribute
        @chosen = false
        @isBlank = isBlank || false
        @ordinal = 0
        @prime = rawData.representation
        super rawData.id

# A generic question o poll
class Question extends BallotItem

    # @param rawData {Object} rawData to build the question
    constructor: (rawData) ->
        @options = []
        @id = rawData.id
        @attribute = rawData.attribute
        @optionsMinChoices = rawData.min
        @optionsMaxChoices = rawData.max
        @blankOption = null
        @ordinal = 0
        super rawData.id

    # Adds an option to the list of options for this question
    # The option is also bound to the question in child-parent relationship,
    # and it's ordinal number is set according to its position in the question
    #
    # @param option {OV.Option} option to add
    # @return {self} (can be chained)
    addOption: (option) ->

        log 'adding option ' + option.representation

        if !(option instanceof Option)
            throw new Error 'Bad argument type, need an Option'

        if option.isBlank
            if @blankOption
                throw new Error 'Question already has a blank option'
            @blankOption = option

        if !option.isBlank
            @options.push option
            option.ordinal = @options.length

        option.parent = @
        @


# A selectable candidate
class Candidate extends BallotItem

    @IS_WRITEIN = 0b0001;
    @IS_BLANK = 0b0010;

    # @param rawData {Object} rawData to build the candidate
    # @param isBlank {boolean} true if this is a blank candidate
    constructor: (rawData, settings = 0) ->
        @id = rawData.id
        @allIds = rawData.allIds
        @attribute = rawData.attribute
        @prime = rawData.representation
        @allRepresentations = rawData.allRepresentations
        @isBlank = ((settings & @constructor.IS_BLANK) == @constructor.IS_BLANK) || false
        @isWriteIn = ((settings & @constructor.IS_WRITEIN) == @constructor.IS_WRITEIN) || false
        @idNumber = ''
        @name = ''
        @chosen = 0
        @ordinal = 0
        @alias = rawData.alias
        super rawData.id

# A generic list of candidates
class List extends BallotItem

    @IS_WRITEIN = 0b0001;
    @IS_BLANK = 0b0010;

    # @param rawList {Object} raw data to build the list
    constructor: (rawList, attribute, settings = 0) ->
        @candidates = []
        @id = rawList.id
        @attribute = attribute
        @idNumber = ''
        @name = ''
        @description = ''
        @chosen = false
        @isBlank = ((settings & @constructor.IS_BLANK) == @constructor.IS_BLANK) || false
        @isWriteIn = ((settings & @constructor.IS_WRITEIN) == @constructor.IS_WRITEIN) || false
        @blankId = null
        @ordinal = 0
        @prime = rawList.representation
        super rawList.id

    # Adds a candidate to the list of candidates
    #
    # @param candidate {OV.Candidate} identifier
    # @return {self} (can be chained)
    addCandidate: (candidate) ->

        log 'adding candidate ' + candidate.prime

        if !(candidate instanceof Candidate)
            throw new Error 'Bad argument type, need a Candidate'

        @candidates.push candidate
        candidate.ordinal = @candidates.length

        candidate.parent = @
        @

    # Marks as blank and sets the blank attribute id
    #
    # @param id {string} blank attribute identifier
    # @return {self} (can be chained)
    setBlank: (blankId) ->

        if @blankId
            throw new Error 'List already has a blank attribute'

        @isBlank = true
        @blankId = blankId
        @


# A generic contest or collection of polls
class Contest extends BallotItem

    # @param rawData {Object} contest data
    constructor: (rawData) ->
        @template = rawData.template
        @validateVoteCorrectness = Function('return ' + rawData.decryptedCorrectnessRule)();
        super rawData.id


class Options extends Contest

    constructor: (rawData) ->
        @questions = []
        super rawData

    # Adds a question to this contest
    # The question is also bound to the contest in child-parent relationship
    # and it's ordinal number is set acccording to its position in the contest
    # s
    # @param option {OV.Question} the question to add
    # @return {self} (can be chained)
    addQuestion: (question) ->

        log 'adding question ' + question.prime

        if !(question instanceof Question)
            throw new Error 'Bad argument type, need a Question'

        @questions.push question
        question.ordinal = @questions.length
        question.parent = @
        @


class ListsAndCandidates extends Contest

    constructor: (rawData) ->
        @allowFullBlank = rawData.fullBlank == 'true'
        @lists = []
        @listQuestion = {} =
            minChoices : 0
            maxChoices : 0
            cumul      : 1
        @candidatesQuestion = {} =
            minChoices  : 0
            maxChoices  : 0
            hasWriteIns : false
            fusions     : []
            cumul       : 1
        super rawData

    # Adds a list of candidates to this contest
    # @param list {OV.List} the list to add
    # @return {self} (can be chained)
    addList: (list) ->

        log 'adding list ' + list.prime

        if !(list instanceof List)
            throw new Error 'Bad argument type, need a List'

        @lists.push list
        list.ordinal = @lists.length
        list.parent = @
        @


# A ballot
class Ballot extends BallotItem

    # @param id {string} identifier
    constructor: (id) ->
        @contests = []
        super

    # Adds a contest to this ballot
    # The contest is also bound to the ballot in child-parent relationship
    #
    # @param option {OV.Contest} the contest to add
    # @return {self} (can be chained)
    addContest: (contest) ->
        if !(contest instanceof Contest)
            throw new Error 'Bad argument type, need a Contest'
        @contests.push contest
        contest.parent = @
        @

module.exports =
    Option: Option
    Question: Question
    Candidate: Candidate
    List: List
    Options: Options
    ListsAndCandidates: ListsAndCandidates
    Ballot: Ballot
