"""Common objects for wizards genearting command files.
"""
"""
    modified by Takeru who belongs
    to Japanese Localization Subcommittee in JSWG (Japan Salome-Meca Working Group)
    on Mar 12 2014
"""

import os
import os.path as osp

from aster_s.utils import Singleton


class CommFilePart(object):
    """Contract for building the command file
    """
    pattern_key = None

    def add_to(self, writer):
        """Add value to command file"""
        raise NotImplementedError


class PatternValue(CommFilePart):
    """A value filling the pattern from a key
    """

    def __init__(self, pattern_key, value):
        self.pattern_key = pattern_key
        self.value = value

    def add_to(self, writer):
        """Add the value to the command file"""
        writer.subs(self.pattern_key, str(self.value))


class ModelType(object):
    """The model type used for the modelisation
    """

    def __init__(self, aster_value, dim):
        self._aster_value = aster_value
        self.dim = dim

    def __repr__(self):
        """Return its Aster value"""
        return self._aster_value


class Modelisation(PatternValue):
    """Modelisation for defining the modele
    """

    def __init__(self, modele_type):
        PatternValue.__init__(self, "modelisation_key", modele_type)

    def give_dim(self):
        """Return the model dimension"""
        return self.value.dim


class Lines(object):
    """Lines producing a part of the command file
    """

    def __init__(self, init_idt="", idt=" " * 4):
        self.init_idt = init_idt
        self.idt = idt
        self._lines = []

    def add(self, line, idt_nb=0):
        """Add a line with an optional indentation number"""
        self._lines.append(self.init_idt + idt_nb * self.idt + line)

    def add_many(self, lines, idt_nb=0):
        """Add several lines with an optional indentation number"""
        for line in lines:
            self.add(line, idt_nb)

    def build_part(self):
        """Build a part of the command file"""
        return os.linesep.join(self._lines)


class Constraint(object):
    """A constraint contract
    """

    def add_to(self, constraints, writer):
        """Add the constraints to the list"""
        raise NotImplementedError


def quote(string):
    """Return a quoted string"""
    return "'%s'" % string


class Arg(object):
    """An argument for the arguments constraint
    """
    _pattern = "%s=%s,"

    def __init__(self, key, value):
        self.line = self._pattern % (key, value)

    def add_prefix(self, prefix):
        """Add a prefix in front of the line"""
        self.line = prefix + self.line

    def add_suffix(self, suffix):
        """Add a suffix at the end of the line"""
        self.line = self.line + suffix


class ArgsConstraint(Constraint):
    """Add several arguments A, B, C in the form:

        _F(keyA=valA,
           keyB=valB,
           keyC=valC,),

    """

    def __init__(self, args):
        self._args = args

    def add_to(self, consts, writer):
        """Add several arguments to constraint lines"""
        args = self._args
        args[0].add_prefix("_F(")
        indent = " " * 3
        for arg in args[1:]:
            arg.add_prefix(indent)
        args[-1].add_suffix("),")
        consts.lines.add_many([arg.line for arg in args], idt_nb=1)



class ConstraintSection(Constraint):
    """A section of constraints"""

    def __init__(self):
        self._consts = []

    def add(self, const):
        """Add a constraint to the section"""
        self._consts.append(const)
        return const

    def write_section(self, name, parent_consts, writer):
        """Write the section with the given name"""
        consts = self._consts
        if consts:
            lines = parent_consts.lines
            lines.add("%s=(" % name)
            for const in consts:
                const.add_to(parent_consts, writer)
            lines.add("),", idt_nb=1)


class Constraints(CommFilePart):
    """Add several constraints
    """
    pattern_key = ""

    def __init__(self):
        self._consts = []
        self.lines = None 

    def add(self, const):
        """Add a constraint"""
        self._consts.append(const)
        return const

    def write_cmd(self, init_cmd, cpl_cmd, writer):
        """Write the constraints command to the command file"""
        lines = Lines()
        self.lines = lines
        if init_cmd:
            lines.add(init_cmd + cpl_cmd)
            lines.init_idt = " " * len(init_cmd)
            for const in self._consts:
                const.add_to(self, writer)
            lines.add(");")
        writer.subs(self.pattern_key, lines.build_part())


class PatternLoader(Singleton):
    """Load command file patterns
    """

    def init(self):
        """Initialize the path where patterns are stored"""
        self._path = osp.dirname(__file__)

    def load(self, fname):
        """Load the command file pattern written in the given filename"""
        fid = open(osp.join(self._path, fname))
        pattern = fid.read()
        fid.close()
        return pattern


def load_pattern(fname):
    """Load the command file pattern written in the given filename"""
    return PatternLoader().load(fname)


class CommWriter(object):
    """The command file writter
    """
    _pattern = "BEGIN(); FIN();"

    def __init__(self):
        self._parts = []
        self._data = {}

    def use(self, part):
        """Add a building part of the command file"""
        # XXX could ensure only a given part type
        self._parts.append(part)
        return part

    def get(self, part_type):
        """Get the first part matching the given type"""
        parts = self.get_all(part_type)
        if parts:
            return parts[0]

    def get_all(self, part_type):
        """Get all the parts matching the given type"""
        parts = []
        for part in self._parts:
            if isinstance(part, part_type):
                parts.append(part)
        return parts

    def subs(self, pattern_key, bloc):
        """Replace the pattern_key by the given bloc"""
        self._data[pattern_key] = bloc

    def write(self, fname):
        """Write the command file in the given filename"""
        data = {}
        self._data = data
        for part in self._parts:
            part.add_to(self)
        fid = open(fname, "w")
        fid.write(self._pattern % data)
        fid.close()


class GrpType(object):
    """Type of group used from Aster
    """

    def __init__(self, aster_grp_type):
        self._grp_type = aster_grp_type

    def __repr__(self):
        """Return the Aster type of group"""
        return self._grp_type


ASTER_GRPS = ["GROUP_MA", "GROUP_NO"]
GRP_MA, GRP_NO = [GrpType(agrp_type) for agrp_type in ASTER_GRPS]


Mode3D = ModelType("3D", 3)
PlaneStress = ModelType("C_PLAN", 2)
PlaneStrain = ModelType("D_PLAN", 2)
AxisSymmetric = ModelType("AXIS", 2)
Plane = ModelType("PLAN", 2)


class YoungModulus(PatternValue):
    """Young modulus for defining material
    """

    def __init__(self, value):
        PatternValue.__init__(self, "young_key", value)


class PoissonRatio(PatternValue):
    """Poisson ratio for defining material
    """

    def __init__(self, value):
        PatternValue.__init__(self, "poisson_key", value)


class Density(PatternValue):
    """Density defining material
    """

    def __init__(self, value):
        PatternValue.__init__(self, "density_key", value)


class DplFromName(ArgsConstraint):
    """Displacement boundary condition
    """
    _keys = ("DX", "DY", "DZ")

    def __init__(self, grp_type, name, dplx=None, dply=None, dplz=None):
        args = [Arg(grp_type, quote(name))]
        for key, dpl in zip(self._keys, (dplx, dply, dplz)):
            if dpl is not None:
                args.append(Arg(key, dpl))
        ArgsConstraint.__init__(self, args)


