001/*
002 * 
003 */
004package de.isas.lipidomics.domain;
005
006import de.isas.lipidomics.palinom.exceptions.ConstraintViolationException;
007import java.util.Collections;
008import java.util.LinkedList;
009import java.util.List;
010import java.util.Map;
011import java.util.Optional;
012import java.util.TreeMap;
013import java.util.stream.Collectors;
014import lombok.Builder;
015import lombok.Data;
016
017/**
018 * A fatty acid with a specific type. This object defines the name, position,
019 * number of carbon atoms, hydroxyls and double bonds, as well as the bond type
020 * to the head group. A FattyAcid can carry optional modifications and can
021 * report double bond positions.
022 *
023 * @author nils.hoffmann
024 */
025@Data
026public class FattyAcid {
027
028    private final FattyAcidType type;
029    private final String name;
030    private final int position;
031    private final int nCarbon;
032    private final int nHydroxy;
033    private final int nDoubleBonds;
034    private final LipidFaBondType lipidFaBondType;
035    private final boolean lcb;
036    private final ModificationsList modifications;
037    private final Map<Integer, String> doubleBondPositions;
038
039    /**
040     * Create a new isomeric level FattyAcid.
041     *
042     * @param name the name, e.g. FA1 for the first FA.
043     * @param position the sn position. -1 if undefined or unknown.
044     * @param nCarbon the number of carbons in this FA.
045     * @param nHydroxy the number of hydroxyls on this FA.
046     * @param lipidFaBondType the bond type, e.g. ESTER.
047     * @param lcb true if this is a long-chain base, e.g. in a Ceramide.
048     * @param modifications optional modifications for this FA.
049     * @param nDoubleBonds the number of double bonds in this FA.
050     * @param doubleBondPositions double bond positions in this FA.
051     * @see LipidFaBondType
052     */
053    @Builder(builderMethodName = "isomericFattyAcidBuilder", builderClassName = "IsomericFattyAcidBuilder")
054    public FattyAcid(String name, int position, int nCarbon, int nHydroxy, LipidFaBondType lipidFaBondType, boolean lcb, ModificationsList modifications, int nDoubleBonds, Map<Integer, String> doubleBondPositions) {
055        this.name = name;
056        if (nCarbon < 0) {
057            throw new ConstraintViolationException("FattyAcid must have at least 0 carbons!");
058        }
059        this.position = position;
060        if (position < -1) {
061            throw new ConstraintViolationException("FattyAcid position must be greater or equal to -1 (undefined) or greater or equal to 0 (0 = first position)!");
062        }
063        this.nCarbon = nCarbon;
064        if (nHydroxy < 0) {
065            throw new ConstraintViolationException("FattyAcid must have at least 0 hydroxy groups!");
066        }
067        this.nHydroxy = nHydroxy;
068        this.lipidFaBondType = Optional.ofNullable(lipidFaBondType).orElse(LipidFaBondType.UNDEFINED);
069        this.lcb = lcb;
070        this.modifications = modifications == null ? ModificationsList.NONE : modifications;
071        if (doubleBondPositions == null) {
072            this.doubleBondPositions = Collections.emptyMap();
073            this.nDoubleBonds = nDoubleBonds;
074        } else {
075            if (nDoubleBonds != doubleBondPositions.size()) {
076                throw new ConstraintViolationException("Isomeric FattyAcid must receive double bond positions for all double bonds! Got " + nDoubleBonds + " double bonds and " + doubleBondPositions.size() + " positions: " + doubleBondPositions);
077            }
078            this.doubleBondPositions = new TreeMap<>();
079            this.doubleBondPositions.putAll(doubleBondPositions);
080            this.nDoubleBonds = this.doubleBondPositions.size();
081        }
082        this.type = FattyAcidType.ISOMERIC;
083    }
084
085    /**
086     * Create a new structural level FattyAcid.
087     *
088     * @param name the name, e.g. FA1 for the first FA.
089     * @param position the sn position. -1 if undefined or unknown.
090     * @param nCarbon the number of carbons in this FA.
091     * @param nHydroxy the number of hydroxyls on this FA.
092     * @param nDoubleBonds the number of double bonds in this FA.
093     * @param lipidFaBondType the bond type, e.g. ESTER.
094     * @param lcb true if this is a long-chain base, e.g. in a Ceramide.
095     * @param modifications optional modifications for this FA.
096     * @see LipidFaBondType
097     */
098    @Builder(builderMethodName = "structuralFattyAcidBuilder", builderClassName = "StructuralFattyAcidBuilder")
099    public FattyAcid(String name, int position, int nCarbon, int nHydroxy, int nDoubleBonds, LipidFaBondType lipidFaBondType, boolean lcb, ModificationsList modifications) {
100        this.name = name;
101        if (nCarbon < 0) {
102            throw new ConstraintViolationException("FattyAcid must have at least 0 carbons!");
103        }
104        this.position = position;
105        if (position < -1) {
106            throw new ConstraintViolationException("FattyAcid position must be greater or equal to -1 (undefined) or greater or equal to 0 (0 = first position)!");
107        }
108        this.nCarbon = nCarbon;
109        if (nHydroxy < 0) {
110            throw new ConstraintViolationException("FattyAcid must have at least 0 hydroxy groups!");
111        }
112        this.nHydroxy = nHydroxy;
113        if (nDoubleBonds < 0) {
114            throw new ConstraintViolationException("FattyAcid must have at least 0 double bonds!");
115        }
116        this.nDoubleBonds = nDoubleBonds;
117        this.lipidFaBondType = Optional.ofNullable(lipidFaBondType).orElse(LipidFaBondType.UNDEFINED);
118        this.lcb = lcb;
119        this.modifications = modifications == null ? ModificationsList.NONE : modifications;
120        this.doubleBondPositions = Collections.emptyMap();
121        this.type = FattyAcidType.STRUCTURAL;
122    }
123
124    /**
125     * Create a new molecular level FattyAcid.
126     *
127     * @param name the name, e.g. FA1 for the first FA.
128     * @param nCarbon the number of carbons in this FA.
129     * @param nHydroxy the number of hydroxyls on this FA.
130     * @param nDoubleBonds the number of double bonds in this FA.
131     * @param lipidFaBondType the bond type, e.g. ESTER.
132     * @param lcb true if this is a long-chain base, e.g. in a Ceramide.
133     * @param modifications optional modifications for this FA.
134     * @see LipidFaBondType
135     */
136    @Builder(builderMethodName = "molecularFattyAcidBuilder", builderClassName = "MolecularFattyAcidBuilder")
137    public FattyAcid(String name, int nCarbon, int nHydroxy, int nDoubleBonds, LipidFaBondType lipidFaBondType, boolean lcb, ModificationsList modifications) {
138        this.name = name;
139        if (nCarbon < 0) {
140            throw new ConstraintViolationException("FattyAcid must have at least 0 carbons!");
141        }
142        this.position = -1;
143        if (position < -1) {
144            throw new ConstraintViolationException("FattyAcid position must be greater or equal to -1 (undefined) or greater or equal to 0 (0 = first position)!");
145        }
146        this.nCarbon = nCarbon;
147        if (nHydroxy < 0) {
148            throw new ConstraintViolationException("FattyAcid must have at least 0 hydroxy groups!");
149        }
150        this.nHydroxy = nHydroxy;
151        if (nDoubleBonds < 0) {
152            throw new ConstraintViolationException("FattyAcid must have at least 0 double bonds!");
153        }
154        this.nDoubleBonds = nDoubleBonds;
155        this.lipidFaBondType = Optional.ofNullable(lipidFaBondType).orElse(LipidFaBondType.UNDEFINED);
156        this.lcb = lcb;
157        this.modifications = modifications == null ? ModificationsList.NONE : modifications;
158        this.doubleBondPositions = Collections.emptyMap();
159        this.type = FattyAcidType.MOLECULAR;
160    }
161
162    /**
163     * Build the name of this substructure.
164     *
165     * @param level the structural lipid level to return this substructure's
166     * name on.
167     * @return the name of this substructure.
168     */
169    public String buildSubstructureName(LipidLevel level) {
170        StringBuilder sb = new StringBuilder();
171        int nDB = 0;
172        int nHydroxy = 0;
173        int nCarbon = 0;
174        nDB += getNDoubleBonds();
175        StringBuilder dbPos = new StringBuilder();
176        List<String> dbPositions = new LinkedList<>();
177        for (Integer key : getDoubleBondPositions().keySet()) {
178            dbPositions.add(key + getDoubleBondPositions().get(key));
179        }
180        if (!getDoubleBondPositions().isEmpty()) {
181            dbPos.
182                    append("(").
183                    append(dbPositions.stream().collect(Collectors.joining(","))).
184                    append(")");
185        }
186
187        nCarbon += getNCarbon();
188        nHydroxy += getNHydroxy();
189        sb.
190                append(nCarbon).
191                append(":").
192                append(nDB).
193                append(dbPos.toString()).
194                append(nHydroxy > 0 ? ";" + nHydroxy : "").
195                append(getLipidFaBondType().suffix());
196        //TODO reenable once LSI has finished modification specification
197//        if (!getModifications().isEmpty()) {
198//            sb.append("(");
199//            sb.append(getModifications().stream().map((t) -> {
200//                return (t.getLeft() == -1 ? "" : t.getLeft()) + "" + t.getRight();
201//            }).collect(Collectors.joining(",")));
202//            sb.append(")");
203//        }
204        return sb.toString();
205    }
206
207    public ElementTable getElements() {
208        ElementTable table = new ElementTable();
209        if (!isLcb()) {
210            if (nCarbon > 0 || nDoubleBonds > 0) {
211                table.incrementBy(Element.ELEMENT_C, nCarbon);// C
212                switch (lipidFaBondType) {
213                    case ESTER:
214                        table.incrementBy(Element.ELEMENT_H, 2 * nCarbon - 1 - 2 * nDoubleBonds); // H
215                        table.incrementBy(Element.ELEMENT_O, 1 + nHydroxy); // O
216                        break;
217                    case ETHER_PLASMENYL:
218                        table.incrementBy(Element.ELEMENT_H, 2 * nCarbon - 1 - 2 * nDoubleBonds + 2); // H
219                        table.incrementBy(Element.ELEMENT_O, nHydroxy); // O
220                        break;
221                    case ETHER_PLASMANYL:
222                        table.incrementBy(Element.ELEMENT_H, (nCarbon + 1) * 2 - 1 - 2 * nDoubleBonds); // H
223                        table.incrementBy(Element.ELEMENT_O, nHydroxy); // O
224                        break;
225                    default:
226                        throw new ConstraintViolationException("Mass cannot be computed for fatty acyl chain with bond type: " + lipidFaBondType);
227                }
228            }
229        } else {
230            // long chain base
231            table.incrementBy(Element.ELEMENT_C, nCarbon); // C
232            table.incrementBy(Element.ELEMENT_H, 2 * (nCarbon - nDoubleBonds) + 1); // H
233            table.incrementBy(Element.ELEMENT_O, nHydroxy); // O
234            table.incrementBy(Element.ELEMENT_N, 1); // N
235        }
236        return table;
237    }
238
239}